1

The question pretty much says all, but let's look into a simple example (I'm using C++, but the question is not strictly related to any particular language):

So, let's say that we have a functor that outputs many objects (e.g. as a tuple). Is it a good idea to store the outputs to the functor instance rather than returning a long tuple?

// some definitions
class OutputTypeA;
class OutputTypeB;
class OutputTypeC;

// Return everything
class FunctorThatReturns
{
    std::tuple<OutputTypeA, OutputTypeB, OutputTypeC> operator()(){} // implementation
};
auto [OutputTypeA out_a, OutputTypeB out_b, OutputTypeC out_c] = FunctorThatReturns()()
// do something with out_a, out_b, out_c
// Store outputs
class FunctorThatStores
{
    OutputTypeA out_a;
    OutputTypeB out_b;
    OutputTypeC out_c;
    void operator()(){} // implementation
}
FunctionThatStores fn;
fn();
//do something with fn.out_a, fn.out_b, fn.out_c

From a theoretical point of view (i.e. functional or OO patterns) what is the best practice for implementing function objects?

Returning to the example, it should be better to return a (potentially) long tuple, because it would allow the functor to behave more like a pure function. How would this affect memory consumption if the objects the functor returns are large? What (if any) are the benefits of storing these large outputs in the functor itself?

Another consideration would be whether the functor will be called once (thanks @Caleth for the comment). Obviously, if the functor is called repeatedly then the outputs would be overwritten. Let's assume the functor is indeed called once and then destroyed. However, can we guarantee that the functor is called once? Should we delegate to the client code (the user of the functor) the responsibility of ensuring that it is called only once?

3
  • 2
    Is it better why? To preserve space? To pass the functor object across layers? As of now, your question is impossible to answer without you providing more details.
    – Andy
    Aug 14 '20 at 13:11
  • 1
    Do you expect your operator() to be called exactly once?
    – Caleth
    Aug 14 '20 at 14:19
  • This is an interesting design question (i.e. design of the functor interface). The reference to C++ in the title might however mislead the readers to think it's a programming question about how to do something in language X. A slight reformulation could easily avoid this confusion and make it more general (i.e. remove C++ from the title, and in the body use C++ as an example; because functors exist also in other languages. [
    – Christophe
    Aug 14 '20 at 23:38
3

Functors are objects that represent functions. In view of this purpose, functors should behave exactly as a function. In many cases, it is even desirable to make them interchangeable with functions, or other functional objects (e.g. a lambda, or an std::function etc...).

Your second approach seems very practical at first sight. But you'd better avoid it since:

  • It breaks the design purpose of functors to be used interchangeably with other functions. So, in a certain way, it kind of breaks the Liskov Substitution Principle applied to functional objects.
  • It also breaks the principle of least knowledge, and creates a coupling with your specific functor, since the using-context has to know about the inner structure of your functor to get the results.
  • It might encourage to do more than one thing in the function, because it's tempting to just add a new member and perform this additional calculation as well.

Between the two, prefer the first approach: it encourages better decoupling from the functor, and is future proof, in that it facilitates flexibility and evolution. Pairs are generally ok, and tuples as well if the different values are of an incompatible type that would immediately be flagged by the compiler in a std::tie assignment.

But there is a third approach: tuples are a frequent source of confusion and mistakes due to their ordering. In fact, I'd pretend that it might create a hidden coupling that also has risks and drawbacks. It may therefore be considered to opt for an explicitly defined return type (like Errorsatz proposed in his/her first paragraph). If it's a throw-away type, a simple struct is sufficient. But maybe you'd discover that there's more behind it. It may look cumbersome at first sight, but it really prevents errors (confusion in the tuple), while at the same time it ensures decoupling and substitutability.

Additional remark: If your functor if for a very complex calculation, it could make sense to use memoization and store already calculated results. However, this is a very different technique: The using context always call the functor like a a normal function. The fact that cached values are used, is encapsulated in the functor and not known by the context.

5

Variables should be declared with the shortest possible lifetime.

Instance variables used as parameters or as return values don't necessarily match that guidance.  That leaves room for error by the consuming client; in this case, the return variables are accessible before calling the function that sets them.

4

I'd recommend the former, because it better fits the semantics of being a functor. If you want named fields, then make a specific class which it returns rather than a Tuple.

That's not to say there's never a purpose for objects that calculate results and store them. Usually this would make sense for something that gets fed data over time and doesn't have the necessary info to calculate results immediately. For example, something like:

class PerformanceMetrics
{
  public:
    void AddSample(const PerformanceData &sample);
    bool IsCalculated() const;
    float AverageFPS() const;
    ...
}

But in that case I wouldn't give it a functor interface because it isn't one.

1
  • +1 for specific return type.
    – Christophe
    Aug 15 '20 at 9:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.