std::bind and lambda functions 4

I have been sloppy in my use of language. I am going to try to be less sloppy. Instead of using function or function object (and I suspect getting them reversed in some cases) I am going to start using the standard-approved term callable object. As always, the standard provides a strict definition, for us it will be good enough to say that a callable object allows us to apply operator() to it. Callable objects include functions, function objects, whatever it is we get back from std::bind, and lambda functions.

In part 3 I said I would reveal the return type of std::bind. If we look up the relevant section of the standard (20.8.9.1.2) we see the following declaration:

template
unspecified bind(F&& f, BoundArgs&&... bound_args);

Unspecified. That doesn’t seem terribly helpful. Of course, this being the standard, there is a definition for unspecified. Section 1.3.25 of the standard states:

unspecified behavior behavior, for a well-formed program construct and correct data, that depends on the implementation [ Note: The implementation is not required to document which behavior occurs. The range of possible behaviors is usually delineated by this International Standard. β€”end note ]

Still not terribly helpful. Perhaps we can find a way to determine the type anyway. By setting up an error condition I can force the compiler to tell me what type it is actually using for the result of std::bind. Assuming that I am binding add in the same way I have been doing, on Visual Studio 2013 I get this for the type:

std::_Bind<true,double,double (__cdecl *const ) (int,float),int,float &>

and on GCC I get this:

std::_Bind_helper<false, double (&)(int, float), int, float&>::type 
{aka std::_Bind<double (*(int, float))(int, float)>}

There are only three problems.

  1. The types are different on the two different compilers (because they use different implementations of the standard library).
  2. Both compilers use types beginning with an underscore – any name starting with an underscore is reserved for the implementation (see 17.6.4.3.2 in the standard).
  3. The standard has already told us clearly and explicitly that the type is unspecified – it can vary between compilers, it can vary between releases of the same compiler, it can vary between updates to the standard library. We cannot rely on the type staying the same.

However much we twist and turn, determining the actual type of the returned value from std::bind is a non starter. Even when we can find out what the type is, we can’t rely on it.

Fortunately, C++11 gives us a way of avoiding knowing the actual type – we can do type deduction.

We can assign the result of std::bind to a variable using auto:

auto fn = std::bind( add, _1, f );

That works for the situations where we can use auto, but doesn’t help us when we want to store the object in a (non-template) class or pass it to a (non-template) function – we can’t declare a parameter as auto.

We’ve already seen one of the other ways we can use type deduction with std::bind. We used std::bind successfully with std::transform, because std::transform is a template function and does type deduction. If we are passing the result to a template function or template class we might can use the result of std::bind without ever needing to know what type it is.

There is a third option. C++11 added decltype. You hand decltype an expression (which it does not evaluate, it just uses it to deduce the type), and it gives us the type of that expression. For example:

auto fn = std::bind( add, _1, f );
typedef decltype( fn ) SingleArgumentAdd;

decltype( fn ) evaluates to the type of fn (at compile time), and we typedef the result to SingleArgumentAdd. This is useful, finally we have an actual type (as in type of an object) that we can type (as in hit keys on the keyboard). It still isn’t perfect though. The example above only works within the current scope – we can’t put the typedef into a header file (at least not without doing other things in the header file that we really shouldn’t). Turns out there is a solution to this problem too. We can do this:

typedef decltype( 
    std::bind( 
        add, 
        _1, 
        std::declval< float >() ) ) SingleArgumentAdd;

We have something new – std::declval. Let’s work out what’s going on here. decltype requires an expression. In our first example of its use we gave it the expression fn. In the second example we want to give it an expression involving std::bind. This means that we need to pass arguments of the right type to std::bind. The first argument is easy – it’s the callable object that we’re trying to wrap. The whole point of this exercise is to wrap that callable object so we need to have it visible. The second argument is also easy, it’s the placeholder _1. For the third argument we must pass it a float value (not the float type, but a value of type float). If we happened to have a float variable in the current scope we could use that. We could also pass it a float constant such as 1.0f. The constant would work well here because it is a nice simple value, but what if it wasn’t a float? What if it was something which required several arguments to its constructor? We don’t want an actual value, just something that represents the value and has the correct type. For that, we can use std::declval. To quote Stroustrup:

The intent is to use declval< X > as a type where the type of a variable of type X is needed.

(The C++ Programming Language Fourth Edition, section 35.4.2)

std::declval does return a value, but we cannot use that value. Since decltype does not evaluate its expression we are safe.

Having jumped through all of those hoops to get the type SingleArgumentAdd it turns out to be a pain to use. When we looked at the definition of unspecified from the standard it stated:

The range of possible behaviors is usually delineated by this International Standard.

That range of possible behaviors for SingleArgumentAdd is pretty small. We already know that the result of std::bind can have operator () applied to it, the standard adds requirements for MoveConstructable and CopyConstructable, but it adds no other requirements. In particular, default construction and assignment are not available:

float f( functionReturningFloat() );
float f1( otherFunctionReturningFloat() );

SingleArgumentAdd fn; // Error, cannot default construct

SingleArgumentAdd fn2 = std::bind( add, _1, f );

fn2 = std::bind( add, _1, f1 ); // Error, cannot assign

Even if we could default construct and assign SingleArgumentAdd it would still be unsatisfactory. Our current definition of SingleArgumentAdd only lets us store the return value from std::bind. Wouldn’t it be nice to have a type that will allow us to store any callable object that has the correct call signature.

A call signature is the name of a return type followed by a parenthesized comma-separated list of zero or more argument types. – C++11 standard, section 20.8.1

The C++11 standard gives us exactly what we want – std::function. Let’s set up a function with the correct parameter and return types:

double singleArgAdd( int i )
{
    return i + 7;
}

Now let’s look at what we can do with std::function:

typedef std::function< double( int ) > SingleArgumentAdd;

SingleArgumentAdd fn; // We can default construct it

fn = std::bind( add, _1, f );  // We can assign the result
                               // of std::bind to it

fn = singleArgAdd;  // We can assign a function 
                    // of the correct signature to it

std::cout << fn( 42 ); // And of course we can call it

std::function wraps a callable object. When we call operator () on a std::function object, it calls operator () on the callable object it is wrapping, known as the target. std::function therefore needs to know what the return type and the parameter types are for operator (). The syntax that we use for the call signature is very similar to the syntax for declaring a function pointer - std::function< double( int ) >

Finally, an uninitialized std::function object is known as empty (and is even displayed like that in the Visual Studio debugger). If we attempt to call an empty std::function, it will throw the exception std::bad_function_call. Fortunately, it is easy to test a std::function object to see if it is empty or not:

SingleArgumentAdd fn2;

// Some code that might or might not assign something to fn2

if( fn2 )
{
    fn2( 42 );
}

That's it for this week, next week we will finally get to lambda functions.

Leave a Reply

Your email address will not be published. Required fields are marked *