Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use variadic perfect forwarding into a lambda?

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;
}
like image 583
kfmfe04 Avatar asked Jan 07 '13 08:01

kfmfe04


2 Answers

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.

like image 150
syam Avatar answered Oct 07 '22 17:10

syam


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 [=].

like image 38
R. Martinho Fernandes Avatar answered Oct 07 '22 18:10

R. Martinho Fernandes