18

I have a working function template that calls a lambda.

I would like to generalize this function template to take variadic arguments and forward them perfectly into the lambda, but I am having trouble getting this code to compile.

I am using gcc 4.7.2.

UPDATE

Using R. Martinho Fernandes's suggestion, I looked up the error in bugzilla - it does look like a bug that's been around for a while. If anyone knows of a workaround (I'm digging around for one now), please post an answer - ty.

ERRORS

junk.cpp: In lambda function:
junk.cpp:32:68: error: parameter packs not expanded with ‘...’:
junk.cpp:32:68: note:         ‘args’
junk.cpp: In instantiation of ‘std::pair<std::basic_string<char>, typename T::Lambda> MP(const string&, M, Args&& ...) [with T = Integer; M = int (Integer::*)()const; Args = {}; typename T::Lambda = std::function<std::function<int()>(const Integer&)>; std::string = std::basic_string<char>]’:
junk.cpp:47:42:   required from here
junk.cpp:34:2: error: using invalid field ‘MP(const string&, M, Args&& ...)::<lambda(const T&)>::__args’
make: *** [junk] Error 1

CODE

#include <functional>
#include <iostream>
#include <map>

struct Integer
{
    typedef std::function<int()>                            Function;
    typedef std::function<Function( Integer const& inst )>  Lambda;

    virtual int getInt() const = 0;
};

struct IntImpl : public Integer
{
    virtual int getInt() const { return 42; }
};

typedef std::function<int()>                               IntFunction;
typedef std::function<IntFunction( Integer const& inst )>  IntLambda;

#define WONT_COMPILE

template<typename T,typename M,typename... Args>
std::pair<std::string,typename T::Lambda>
MP( std::string const& str, M method, Args&&... args )
{
#ifdef WONT_COMPILE 
    return std::make_pair( str, 
        [=]( T const& inst ) 
        {
            // COMPILE ERROR (Line 32) on next line
            return std::bind( method, std::cref( inst ), std::forward<Args>(args)...);
        } 
    );
#else
    return std::make_pair( str, 
        [method]( T const& inst ) 
        {
            return std::bind( method, std::cref( inst ));
        } 
    );
#endif
}

std::map<std::string,IntLambda> const g_intTbl =
{
    MP<Integer>( "getInt", &Integer::getInt )
};

int
main( int argv, char* argc[] )
{
    IntImpl x;
    std::cerr << g_intTbl.find("getInt")->second( x )() << std::endl;
}
kfmfe04
  • 14,936
  • 14
  • 74
  • 140

2 Answers2

12

If anyone knows of a workaround (I'm digging around for one now), please post an answer

I ran into the exact same problem and found a workaround. It's kind of a late answer, hope you found a solution in the meantime, but here it is anyway (it could at the very least be useful to others).

The idea is to change the lambda's parameters so that it also accepts the same variadic arguments as the outer function (eg. [](int) {} becomes [](int, Args&&... args) {}) and bind the lambda to the outer function's variadic arguments. Once this is done, no more problem forwarding the variadic arguments inside the lambda.

To sum it up:

template<typename... Args>
std::function<void (int)> foo(Args&&... args) {
    return [&](int bar) {
                  // COMPILER BUG: doesn't work with GCC 4.7 despite the capture
                  doSomething(bar, std::forward<Args>(args)...);
              };
}

template<typename... Args>
std::function<void (int)> foo(Args&&... args) {
    return std::bind([](int bar, Args&&... args) {
                            // now this works with GCC 4.7
                            doSomething(bar, std::forward<Args>(args)...);
                       },
                     std::placeholders::_1, std::forward<Args>(args)...);
}

Sure this is an ugly hack, but at least you can still get the intended functionality even when you're stuck with a buggy compiler.

syam
  • 14,701
  • 3
  • 41
  • 65
  • Does this work as written? I think you need to change the signature of the return value to take into account the fact that the std::function is taking `int` and `Args&&...` as parameters. – Sean Lynch Nov 18 '13 at 17:58
  • @Sean No, the code I posted works as is. There is no need to change the type of `foo`'s return value because the `Args&&...` part is *bound* before returning the functor. That's the whole point of `bind` actually: partial application of the bound functor's arguments. – syam Nov 19 '13 at 07:06
  • Yes I had a major misunderstanding of what bind is actually for, Thanks for clearing this up. – Sean Lynch Nov 19 '13 at 15:25
  • 1
    Wouldn't the first example with `[&]` not be perfectly-forwarded because it would be reference capture, not perfect-forwarding capture? – Claudiu Feb 08 '16 at 20:21
7

This appears to be a fault of the compiler (please report it if it isn't already). The standard says:

A pack expansion consists of a pattern and an ellipsis, the instantiation of which produces zero or more instantiations of the pattern in a list (described below). The form of the pattern depends on the context in which the expansion occurs. Pack expansions can occur in the following contexts:

— [...]
— In a capture-list (5.1.2); the pattern is a capture.
— [...]

This makes your code correct.

Until you get a compiler that can handle this, you will have capture everything as a workaround, with [=].

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • Heh I was just about to write the same (but you have nicer standard quotes). Specifically it's a gcc bug - MSVC (november CTP) and Clang 3.2 accept argument pack expansions in lambda captures. – Andrei Tita Jan 07 '13 at 08:20
  • +1 for shining light on the problem : if anyone knows a gcc workaround, please post - ty – kfmfe04 Jan 07 '13 at 08:23