More Perfect Forwarding N3466=12-0156
2012-11-03
Mike Spertus
mike_spertus@symantec.com

More Perfect Forwarding

The problem

The moment a programmer first encounters multithreaded programs, they run into surprises like the following example: // Inspired by examples in section 2.2 of Anthony Williams C++ Concurrency in Action void f(int &i) { i = 2; /* ... */ } int i; f(i); // i passed by reference, f can change i thread tf = thread(f, i); // i not passed by reference, so changes to i are silently discarded While they can always force a pass by reference,e.g., by thread t(f, ref(i)); this is entirely different from how functions arguments are ordinarily passed, and omitting the ref is likely to result in a silent failure. We can even have silent errors introduced for parameters that are passed by value // Another variation on an example in section 2.2 of Anthony Williams C++ Concurrency in Action void f(int i,std::string s); // Unlike in the book, s is passed by value. void my_oops(int some_param) { char buffer[1024]; sprintf(buffer, "%i",some_param); // Intermittent failures as buffer may not be converted to a string during the lifetime of buffer std::thread t(f,3,buffer); t.detach(); }

I believe requiring most C++ programmers to be conversant with these kinds of silent change of signature is a pretty high bar. This is borne out by my experience teaching C++ to Masters' students, possibly because the reason for this change isn't apparent until one is comfortable writing templates, instead they naturally expect asynchronous functions to obey the same parameter passing rules as ordinary functions.

Note: All of the above discussion applies equally to bind.

Proposed solution

The underlying problem for this and similar problems is that we cannot deduce the function signature of a callable template. std::result_of can give us the return type but not the parameter types.

We propose addressing this with a (compiler-supported) type trait std::signature whose type typedef is the function type of the callable object when called with the given parameter types.

For example, if we define C as

struct C { int operator()(double d, int i); int operator()(double d1, double d2); }; then signature<C(int, int)>::type would be int(double, double).

As an extended example, we can create a new async that avoids the above problems by taking its arguments with the same signature as its callable does:

// more_perfect_forwarding_async.h #include<iostream> #include<utility> #include<future> #include<type_traits> // Uses "apply" template for unpacking tuple members into arguments written by DRayX at // http://stackoverflow.com/questions/687490/how-do-i-expand-a-tuple-into-variadic-template-functions-arguments #include"apply.h" template<typename Callable, typename Signature> struct Caller; template<typename Callable, typename Result, typename...Args> struct Caller<Callable, Result(Args...)> { Caller(Callable c, Args&&... args) : callable(c), saved_args(std::forward<Args...>(args...)) {} Result operator()() { return apply(callable, saved_args); } std::tuple<Args...> saved_args; Callable callable; }; template<typename Callable, typename... Args> future<typename result_of<Callable(Args...)>::type> more_perfect_forwarding_async(Callable c, Args&&... args) { return std::async(Caller<Callable, typename signature<Callable(Args...)>::type>(c, std::forward<Args>(args)...)); }
Note: This uses complicated template metafunction given by DRayX on StackOverflow to unpack tuple members into a function argument list . See N3416: Packaging Parameter Packs for an alternative approach not requiring metaprogramming.

Now, we can run asynchronous functions without worrying about implicit signature changes

#include"more_perfect_forwarding_async.h" #include<iostream> int func(int &i) { i = 2; return i; } int main() { int i = 5; auto f = more_perfect_forwarding_async(func, i); f.get(); cout << "i = " << i << endl; // Correctly prints 2 }

Implementation status

I validated the code for more_perfect_forwarding_async on g++ 4.7.2 with an explicit specialization for signature. A compiler-supported signature type trait is expected for Bristol.