Channels ▼
RSS

C++ Function Objects in TR1


May, 2005: C++Function Objects in TR1

Pete Becker is a software developer employed by Dinkumware Ltd., where he works on Standard Library implementation and documentation for C, C++, and Java. He is Project Editor for the C++ Standard, and for several years wrote a column for The C/C++ Users Journal. He is currently writing a book about TR1. He can be contacted at petebecker@ acm.org.


The "Technical Report on C++ Library Extensions" (TR1) [1] introduces four new function object templates. These templates use more sophisticated implementation techniques than the function object templates in the C++ Standard Library. As a result, they are more flexible than the older templates. This flexibility can lead to some surprises, though, when you write code that mixes the old and the new templates. In this article, I examine what the new function object templates do, how to use them, and how they interoperate with the templates in the Standard Library.

Discussions of function objects are often confusing because the same words are used to mean different things. The Library Technical Report introduces several terms of art for discussing and specifying what function objects do. First, a "callable type" is a pointer to a function, a pointer to a member function, a pointer to member data [2], or a class type whose objects can appear to the left of a function call operator [3]. Naturally, a "callable object" is an object of a callable type. Next, a "call wrapper type" is a type that holds a callable object and supports a call operation that forwards to that object; a "call wrapper" is an object of a call wrapper type. A "target object" is the callable object held by a call wrapper object.

Function Objects in the C++ Standard

In the C++ Standard, the header <functional> defines several templates that you can use to create callable types. These callable types are often used to create objects that are passed to algorithms.

Some of these callable types are "predicates" that tell you whether some proposition is true. For example, the class std::equal_to<int> has a function call operator that takes two arguments of type const int& and returns a bool, with the value true if the arguments are equal and false if they aren't. See Listing 1, which uses the predicate equal_to<int> and the algorithm unique to remove duplicate elements in an array [4] object.

Some of these callable types are "operations" that perform some logical or arithmetic operations on their arguments. For example, the class std::negate<double> has a function call operator that takes an argument of type const double& and returns a double that is the negation of its argument. See Listing 2, which uses negate<double> to negate the elements in an array object.

And finally, some of these types are call wrapper types. For example, the template std::binary_negate<T> has a function call operator that takes two arguments; the first has type const T::first_argument_type& and the second has type const T::second_argument_ type&. It returns a value of type bool, which is the logical negation of the result of T's operator(). For example, the function call operator defined by binary_negate<not_equal_ to<double> > takes two arguments of type const int& and returns true if the arguments are equal, otherwise false [5]; see Listing 3.

The header <functional> also introduces template functions to simplify creating call wrapper objects. Listing 4 is the same code as Listing 3, except that the binary_negate object is created through a function call rather than an explicit constructor. This form is easier to write and maintain because you don't have to repeat the type of the target object.

Function Objects in TR1

The function objects introduced in TR1 are all call wrapper types. Two of these templates, std::tr1::reference_wrapper and std::tr1:: function, can be used directly. The names and template argument types of the other two templates are unspecified; objects of these types are created by calling the template functions std::tr1::mem_fn and std::tr1:: bind. TR1 also provides two template functions, std::tr1::ref and std::tr1::cref, for creating objects whose types are specializations of the template reference_ wrapper.

One problem that C++ programmers sometimes run into is that references can't be copied. As a result, the compiler can't generate a default assignment operator for a class that contains a reference, so a programmer who writes such a class also has to write an assignment operator for it. The reference_wrapper template avoids this problem: An object of type reference_wrapper<T> acts like a reference to T, but it can be copied.

In addition to this primary benefit, the template reference_wrapper can also be used to create a call wrapper type. You do this by creating a reference_wrapper that wraps a callable object; see Listing 5.

The template function provides polymorphic call wrapper types. When you create a function object, you specify the signature that its function call operator should provide, using a "function type" declaration. A function type is simply a return type followed by an opening parenthesis followed by a comma-separated argument list (which can be empty) followed by a closing parenthesis [6]. For example, double(double) is a type declaration for a function that takes an argument of type double and returns a value of type double; double(double,double) declares a function that takes two arguments of type double and returns a value of type double. The declaration function<double(double)> defines a type that has a function call operator that takes an argument of type double and returns a value of type double [7].

When you create an object whose type is a specialization of function, you can assign any callable object to it, provided that the callable object can be called with arguments of the type specified in the signature and its return type can be converted to the type specified in the signature. In Listing 6, the C function cosl takes an argument of type long double and returns a value of type long double. We use its address to initialize the function<double(double)> object named func. The template function takes care of the argument conversion and the return type conversion. The C function sinf takes an argument of type float and returns a value of type float. It, too, can be the target object of func, and again the function template handles the argument and return value conversions.

The template function mem_fn creates call wrappers that wrap pointers to member functions and pointers to member data. It's a generalization of the standard template functions std::mem_fun and std::mem_fun_ref, and it's much easier to use. As the names indicate, when you use mem_fun and mem_fun_ref, you have to decide at the point where you create the call wrapper whether you're going to use it with a pointer to an object or a reference to an object. With mem_fn, you don't have to make this decision; the object returned by the function can be called with a pointer to an object, a reference to an object, or a smart pointer that points to an object; see Listing 7.

The template function bind creates call wrappers that can have a different number of arguments and a different order of arguments from their target object. It's a generalization of the functions std::bind1st and std::bind2nd. Where each function from the Standard Library binds exactly one argument at a fixed position, bind binds as many arguments as you want (up to an implementation-specific limit) in any position you want. When you call bind, the first argument is the callable object that will be the target object of the returned object. There must be as many additional arguments in the call to bind as there are arguments to the callable object. These additional arguments tell bind how to determine the actual argument to be passed to the callable object. When the actual argument is simply a value, that value is passed to the callable object. When the actual argument is a placeholder (placeholders are named _1, _2, and so on, up to an implementation-specific limit), the value passed to the call wrapper's function call operator is passed to the callable object. For example, the call bind(cos, 1.0) creates an object whose function call operator ignores its arguments and returns cos(1.0). The call bind(cos, _1) returns an object whose function call operator ignores all its arguments except the first and returns the result of calling cos with its first argument [8]; see Listing 8.

Design Differences

You might have noticed that all of the callable objects defined by the Standard Library have very specific argument types and return types. For example, as mentioned, std::negate<double> takes one argument of type const double&, and returns an object of type double. The specifications for the callable objects defined by TR1, except for function, are far less specific. That's because the function call operators in the Standard Library are ordinary member functions defined in the templates that define the callable types; in TR1 they're template member functions. For example, in the Standard Library, the function call operator for a callable wrapper that simply forwards to its target object would look like this (many details omitted):

// Standard Library Style
template <class Obj>
struct forward :
  unary_function<typename Obj::argument_type, typename Obj::result_type> {
typename Obj::result_type operator()(
  const typename Obj::argument_type& arg) const
  { return obj(arg); }
private:
  Obj obj;
};

This template requires the callable type that's passed as the template argument to have two nested type names, result_type and argument_type. It uses those type names to determine its argument type and return type. If it wrapped a callable object that took two arguments, it would need three nested types: result_type, first_argument_type, and second_argument_type [9]. The template also defines its own versions of result_type and argument_type, through the template unary_function, in case you want to use a specialization of it as the type argument to another call wrapper type.

In TR1, call wrapper types infer their argument types from the arguments passed to the function call operator. With this approach, our forwarding template would look like this:

// TR1 Style
template <class Obj>
struct forward {
template <class Arg>
typename result_of<Obj(Arg)>::type operator()(
  Arg& arg) const
  { return obj(arg); }
private:
  Obj obj;
};

There are two major differences from the earlier version. In the Standard Library version, the argument to the function call operator is passed by const reference; in the TR1 versions, there's no const. This makes it possible for the function call operator to modify its argument, which the Standard Library version can't do. On the other hand, as written, if you try to pass an argument that isn't a modifiable lvalue, you'll get an error. That's why the template function show in Listing 8 creates two autovariables initialized to 1 and 2.1, instead of simply passing those values directly to the call wrapper. The display function takes its argument by value, so it can accept an argument that isn't modifiable, but the TR1 style of forwarding doesn't support passing nonmodifiable arguments [10].

The other major difference is the return type of the function call operator. It's determined by using the template std::tr1:: result_of<Obj(Arg)>, which defines a nested type named type that is the return type of obj(arg), where obj is an object of type Obj and arg as an object of type Arg [11]. Using the actual argument type to determine the return type allows call wrapper types to wrap callable types that can have more than one return type. For example:

struct confusing {
int operator()(int);
double operator()(double);
};

If we instantiate forward<confusing>, what's the return type of forward::operator()? We can't say, because it calls either confusing::operator()(int) or confusing::operator()(double), depending on the argument that it's called with. Because of this possibility, confusing can't have a nested type that gives its return type [12]. So TR1 function objects, except function, use result_of to determine the return type of their function call operators at the point where the function call operator is called.

Mixing Standard Library And TR1 Function Objects

Again, there are cases where TR1-style call wrapper types cannot define a nested type that gives the return type of their function call operators. Since Standard Library-style call wrappers depend on result_type to determine the return type of their function call operators, this sometimes means that you can't use a TR1-style call wrapper type as the template argument to a Standard Library call wrapper type. In many cases, though, we know from the template argument to the call wrapper type exactly what the return type of the function call operator is, so it's possible in those cases for the call wrapper type to define result_type.

Similarly, although most of the function call operators define their argument types based on the actual call, in many cases, we know from the template argument what constraints those arguments must meet. For example, for any call wrapper type that wraps a pointer to a function, the arguments to the function call operator must be convertible to the argument types that the function requires.

Although TR1-style call wrappers don't need these nested type names, they provide them, in most cases, for compatibility with the Standard Library.

When function is instantiated with a call signature that has one argument, it is derived from std::unary_function<T1, R>, where T1 is the type of the argument and R is the return type. When it is instantiated with a signature that has two arguments, it is derived from std::binary_function<T1, T2, R>.

When mem_fn is called with a pointer to a member function of a class T that takes no arguments and returns an object of type R, the type of the object that mem_fn returns is derived from unary_function<T*, R>. When it is called with a pointer to a member function that takes one argument of type T1, the returned object is derived from binary_function<T*, T1, R>. When it is called with a pointer to member data, the returned object is not derived from either of these templates and it does not define any of the nested type names.

Because it can be instantiated with more types, the reference_wrapper template is a bit more complicated. It is derived from unary_function<T1, R> when it is instantiated with a pointer to a function that takes one argument of type T1 and returns R, when it is instantiated with a pointer to a member function of a class T that takes no arguments, in which case T1 is a pointer to T, and when it is instantiated with a callable type that is derived from std::unary_ function<T1, R>. It is derived from binary_function<T1, T2, R> in the obvious cases.

The type of the object returned by bind is not derived from unary_function or from binary_function. It does, however, define result_type when that type is well defined; namely, when wrapping a pointer to function, a pointer to member function, or a callable type that defines a nested type named result_type. It does not define any of the names that are synonyms for argument types.

The callable types defined in the Standard Library use the nested types that we've just been looking at; this means that they can only be instantiated for the types that define those nested types. Listing 9 shows this in action. The first call to not2 is okay because the type returned by mem_fn defines the required types. The second call to not2 doesn't compile because the type returned by bind does not define those types.

Conclusion

The Standard Library uses a straightforward, rigid approach to writing callable types. TR1 introduces a more flexible approach that is harder to implement but is often easier to use. Types written using the new approach can, in most cases, honor the conventions established in the Standard Library, although this sometimes means that some things the new code can do aren't available when it is used with old code. That's the price of backward compatibility.

Notes

  1. [1] TR1 is currently progressing through the ISO approval process. A recent draft is available at http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1745.pdf. The company that I work for, Dinkumware Ltd. (http://www.dinkumware.com), is implementing the library.
  2. [2] You might be surprised to see pointer to member data in this list; a pointer to member data is obviously not like a function. But the new template function mem_fn can create objects that can bind a pointer to member function and a pointer or reference to an object; this uses the same function call syntax as an actual function call, so pointers to member data are callable types.
  3. [3] This wording may seem a little convoluted, but it was carefully written to not require that the type define a function call operator. That leaves open the possibility that a function object can provide a conversion operator that returns a pointer to a function; when such an object appears to the left of a function call operator, the compiler uses the conversion operator to get the function pointer and then calls the function it points to.
  4. [4] These example programs use the new TR1 template named array, which is a fixed-size array of objects of a designated type. The first template argument specifies the element type and the second template argument specifies the number of elements. One nice thing about the array template is that it can be initialized with a brace-enclosed initializer, just like a built-in array.
  5. [5] Of course, this can be done more easily with the predicate equal_to, but that would not demonstrate the use of call wrappers.
  6. [6] This is actually a vestigial offshoot of the C grammar. In C, there's nothing useful you can do with a function type except declare a pointer to that type; in practice, we always skip the function type and declare pointer-to-function types instead.
  7. [7] Well, not quite; see note 2.
  8. [8] std::tr1::bind(func, val, _1) is equivalent to std::bind1st(func, val), and std::tr1::bind(func, val, _2) is equivalent to std::bind2nd(func, val).
  9. [9] The Standard Library provides two templates, std::unary_function and std::binary_function, that provide these type definitions. All of the callable types defined in the Standard Library are derived from one of these templates. You don't have to use them in your code if you don't want to, but if you don't use them, you have to write the type definitions yourself. If you do use them, be careful: The template arguments go the wrong way. You'd use binary_function<int, int, double> for a function object that takes two arguments of type int and returns double. That's the opposite of the C and C++ conventions, undoubtedly a vestige of earlier attempts to implement the STL in Ada.
  10. [10] In this simple example, we could add a second template function call operator that takes an argument of type const Arg& to allow calling with nonmodifiable values. But that doesn't work very well for function call operators that take more than one argument; we'd have to provide 2n overloaded functions to handle n arguments. Nobody has yet figured out a way to write these templates to avoid this combinatorial explosion.
  11. [11] Getting this right requires help from the compiler; it cannot be implemented in portable C++ code. Because compilers do not currently provide the help that's needed, TR1 contains a formula for library implementors to use that's well defined in most cases, and usually correct.
  12. [12] This is also a problem when the target object is a pointer to member data. From the class definition, we know the basic type of the data that the pointer points to, but when we apply that pointer to an object, the result has the same const or volatile qualifiers as the object, so again we don't know the actual return type until we call the function call operator.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video