A uniform method for computing function object return types


Document number: N1437=03-0019
Date: 27 February 2003
Project: Programming Language C++, Library Working Group
Reply-to: Douglas Gregor

Introduction

This proposal introduces a class template result_of that computes the return type of a function object call. result_of is intended to act as a bridge from the function objects of C++98 to more powerful function objects as used in current binding and composition libraries. This bridge provides both forward compatibility, allowing C++02 code to derive benefits from future C++ revisions without modification, and backward compatibility to enable next-generation libraries to retain compatiblity with C++98 and C++02 compilers.

Function objects provide customization for standard library algorithms and are therefore important for the effective use of these algorithms. Experience has shown that programs using function objects generally use a large number of very simple function objects. Many of these function objects merely reorder, drop, or introduce values for parameters, and then forward to other functions; they are function object adaptors.

The design of function object adaptors reduces to writing forwarding functions that can accept a set of arguments, transform those arguments in some manner, and pass the resulting arguments to another function. There are two important considerations when writing any forwarding function, regardless of the argument transformation: how to forward the arguments to the underlying function object and how to return the result returned by the underlying function object. The former is discussed extensively in [Dimov02]; the latter is the subject of this proposal.

Existing Practice

The C++98 standard requires that unary and binary function objects define a member type result_type specifying the type returned from operator(). While this method works well for simple function objects (and has been adopted by even very flexible composition and binding libraries, such as Boost.Bind [Boost01]), it is incompatible with function objects with an overloaded or templated operator(). These function objects may not have values of the same type returned from every instance of operator(), and the result type may not even be known until a complete set of argument types is given. Thus it is impossible to define a single result_type member type. Such function objects do not fit the function object model of the standard library, and therefore modern binding and composition libraries have defined new (somewhat incompatible) methods for deducing return types.

The basic method employed by most libraries that require return type deduction beyond what result_type can provide is to require each function object to define a member template that accepts a list of argument types and defines a member type that indicates the return type of the function object when called with arguments of the given types. The specific details vary among libraries: the argument type list may be specifed via separate template arguments or it may be bundled together in a typelist of some form; the return type deduction is sometimes performed by a namespace-level class template that forwards to a member template (e.g., actor_result in Phoenix [Guzman02]); and the names of these entities differ among implementations (e.g., result in Phoenix [Guzman02] and sig in Lambda [Boost02]). The following example defines a simple function object whose return type depends on its argument type:

struct square_ {
  template<typename T> struct result {
    typedef T type;
  }

  template<typename T> T operator()(T& x) const
  {
    return x*x;
  }
};

Syntax and examples

The result_of class template follows the existing practice. It defines a namespace-level class template (result_of) that determines the return type of a function object given the argument types to be passed to that function object, and defines two methods for retrieving the function call operator return type from the function object type: the first uses result_type, for backward compatibility with simple function objects, and the second uses a nested result member class template, for use with more powerful function objects.

The definition of the behavior of result_of is straightforward: given types F, T1, T2, ..., TN and lvalues f, t1, t2, ..., tN of those types, respectively, the type expression

result_of<F(T1, T2, ..., TN)>::type

evaluates to the type of the expression f(t1, t2, ..., tN).

A generic forwarding function object may then be defined as:

template<typename F> struct forwardN {
  template<typename Args> struct result;

  template<typename T, typename T1, typename T2, ..., typename TN>
  struct result<T(T1, T2, ..., TN)> {
    typedef typename result_of<F(T1, T2, ..., TN)>::type type;
  };

  template<typename T, typename T1, typename T2, ..., typename TN>
  struct result<const T(T1, T2, ..., TN)> {
    typedef typename result_of<const F(T1, T2, ..., TN)>::type type;
  };

  template<typename T1, typename T2, ..., typename TN>
  typename result<forwardN(T1, T2, ..., TN)>::type
  operator()(T1& t1, T2& t2, ..., TN& tN) 
  {
    return f(t1, t2, ..., tN);
  }

  template<typename T1, typename T2, ..., typename TN>
  typename result<const forwardN(T1, T2, ..., TN)>::type
  operator()(T1& t1, T2& t2, ..., TN& tN) const
  {
    return f(t1, t2, ..., tN);
  }

  F f;
};

Looking Ahead

The problem of determining the return type of a function object call is a subset of the more general problem of determining the result type of an expression. If this larger problem is solved (e.g., by a form of typeof[Stroustrup02]) in future revisions of C++, that solution may be used directly with the proposed result_of, as may vendor-specific extensions with similar functionality. It is the intention of this proposal that with a suitable typeof implementation (i.e., one that preserves references and cv-qualifiers) the definition of result_of would be as trivial as:

template<typename F, typename T1, typename T2, ..., typename TN>
class result_of<F(T1, T2, ..., TN)> {
  static F f;
  static T1 t1;
  static T2 t2;
       .
       .
       .
  static TN tN;
  
public:
  typedef typeof(f(t1, t2, ..., tN)) type;
};

The advent of typeof should not be viewed as nullifying this proposal. Much to the contrary, this proposal defines a method by which C++98 libraries can both benefit today from a simple, powerful method for deducing function object return types and be prepared for tomorrow's language support for return type deduction. Using the proposed result_of, a C++98 forwarding function object such as the aforementioned forwardN will automatically use any advances in return type deduction as they become available in newer compiler and standard library implementations.

Impact on the Standard

This proposal defines a pure extension to the header <functional>. It introduces only one additional name, result_of, into namespace std.

Proposed Text

Header <functional>

namespace std {
  template<typename FunctionType> class result_of;
}
Class template result_of
template<typename FunctionType  // Function type F(T1, T2, ..., TN)
         > 
class result_of {
public:
  // types
  typedef unspecified type;
};

Given an lvalue f of type F and lvalues t1, t2, ..., tN of types T1, T2, ..., TN, respectively, the type member type defines the result type of the expression f(t1, t2, ..., tN).

The implementation may determine the member type type via any means that produces the exact type of the expression f(t1, t2, ..., tN) for the given types.

If the implementation cannot determine the type of the expression f(t1, t2, ..., tN), or if the expression is ill-formed, the implementation shall use the following process to determine the member type type:

  1. If F is a function type, type is the return type of the function type F.

  2. If F is a member function type, type is the return type of the member function type F.

  3. If F is a function object defined by the standard library, the method of determining type is unspecified.

  4. If F is a class type with a member type result_type, type is F::result_type.

  5. If F is a class type with no member named result_type or if F::result_type is not a type:

    1. If N=0 (no arguments), type is void.
    2. If N>0, type is F::result<F(T1, T2, ..., TN)>::type.
  6. Otherwise, the program is ill-formed.

Rationale

  1. result_of syntax: the result_of syntax differs from existing practice. Existing libraries generally pass the argument types as individual template arguments or via a tuple-like typelist facility. However, it should be noted that these libraries predate the use of function types to encode operations and argument lists. There are several other advantages to the function type encoding:

    • The syntax of the type computation closely mirrors the syntax of the run-time computation.

    • The function type includes in a natural way the type of the function object (even when forwarding to the result member class template), allowing for differentiation between invocations of a function object with differing cv-qualifiers.

    • Limitations on the number of argument types are not introduced by the result_of class template.

    • Auxiliary typelist types are not introduced.

    • With the acceptance of the function object wrappers proposal [Gregor02], there is precedent for the user of function types to encode function argument lists.

  2. result_of<F()>::type: the zero-argument case must differ from cases with one or more arguments when the return type is not easily deducible (e.g., via a function pointer, member function pointer, implementation-specific deduction, or result_type member) due to the C++ template class instantiation rules. When a template class is instantiated, the declarations of every non-template member function are instantiated. This poses a problem for function objects attempting to accept zero arguments, because the declaration of the zero-argument operator() (and, correspondingly, result_of<F()>) will be instanted even if the function is not called. However, the underlying function object may not accept zero arguments (and, thus, F::result<F()>::type may be ill-formed).

    The cost of this special case is that function objects accepting zero arguments must specialize result_of for zero arguments or provide a single result type via result_type.

Acknowledgements

Jaakko Järvi, Peter Dimov, Joel de Guzman, and Gary Powell were all instrumental in the specification of result_of.

References

[Boost01] Boost.Bind library, September 2001. Available online at http://www.boost.org/libs/bind/.

[Boost02] Boost.Lambda library, April 2002. Available online at http://www.boost.org/libs/lambda/.

[Dimov02] Peter Dimov, Howard E. Hinnant, Dave Abrahams, The Forwarding Problem: Arguments, C++ committee document N1385=02-0043, September 2002. Available online at http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2002/n1385.htm

[Dimov03] Peter Dimov, Douglas Gregor, Jaakko Järvi, and Gary Powell, A Proposal to Add an Enhanced Binder to the Library Technical Report, C++ committee document N1438=03-0020, February 2003. Available online at http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1438.html.

[Gregor02] Douglas Gregor, A Proposal to add a Polymorphic Function Object Wrapper to the Standard Library, C++ committee document N1402=02-0060, October 2002. Available online at http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2002/n1402.htm.

[Guzman02] Joel de Guzman, Phoenix library documentation, October 2002. Available online at http://spirit.sourceforge.net/index.php?doc=docs/phoenix v1 0/index.html.

[Hinnant02] Howard E. Hinnant, Peter Dimov, and Dave Abrahams, A Proposal to Add Move Semantics Support to the C++ Language, C++ committee document N1377=02-0035, September 2002. Available online at http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2002/n1377.htm.

[Stroustrup02] Bjarne Stroustrup, auto/typeof, C++ reflector message c++std-ext-5364, October 2002.