C++14: Generic lambda with generic std::function a

2020-03-17 04:20发布

问题:

Consider this pseudo-snippet:

class SomeClass
{
public:
    SomeClass()
    {
        if(true)
        {
            fooCall = [](auto a){ cout << a.sayHello(); };
        }
        else
        {
            fooCall = [](auto b){ cout << b.sayHello(); };
        }
    }
private:
    template<typename T>
    std::function<void(T)> fooCall;
};

What I want is a class member fooCall which stores a generic lambda, which in turn is assigned in the constructor.

The compiler complains that fooCall cannot be a templated data member.

Is there any simple solution on how i can store generic lambdas in a class?

回答1:

There is no way you'll be able to choose between two generic lambdas at run-time, as you don't have a concrete signature to type-erase.

If you can make the decision at compile-time, you can templatize the class itself:

template <typename F>
class SomeClass
{
private:
    F fooCall;

public:
    SomeClass(F&& f) : fooCall{std::move(f)} { }
};

You can then create an helper function to deduce F:

auto makeSomeClassImpl(std::true_type) 
{
    auto l = [](auto a){ cout << a.sayHello(); };
    return SomeClass<decltype(l)>{std::move(l)};
}

auto makeSomeClassImpl(std::false_type) 
{
    auto l = [](auto b){ cout << b.sayHello(); };
    return SomeClass<decltype(l)>{std::move(l)};
}

template <bool B>
auto makeSomeClass() 
{
    return makeSomeClassImpl(std::bool_constant<B>{});
}


回答2:

I was not able to store std::function<> as a generic lambda in the class directly as a member. What I was able to do was to specifically use one within the class's constructor. I'm not 100% sure if this is what the OP was trying to achieve but this is what I was able to compile, build & run with what I'm suspecting the OP was aiming for by the code they provided.

template<class>
class test {
public: // While testing I changed this to public access...
        // Could not get object below to compile, build & run
    /*template<class U = T>
    static std::function<void(U)> fooCall;*/
public:
   test();
};

template<class T>
test<T>::test() {
    // This would not compile, build & run
    // fooCall<T> = []( T t ) { std::cout << t.sayHello(); };

    // Removed the variable within the class as a member and moved it here
    // to local scope of the class's constructor
    std::function<void(T)> fooCall = []( auto a ) { std::cout << a.sayHello(); };
    T t; // created an instance of <Type T>
    fooCall(t); // passed t into fooCall's constructor to invoke the call.
}

struct A {
    std::string sayHello() { return "A say's Hello!\n"; }
};

struct B {
    std::string sayHello() { return "B say's Hello!\n"; }
};


int main() {
    // could not instantiate an object of SomeClass<T> with a member of
    // a std::function<> type that is stored by a type of a generic lambda.

    /*SomeClass<A> someA;
    SomeClass<B> someB;
    someA.foo();
    someB.foo();*/

    // Simply just used the object's constructors to invoke the locally stored lambda within the class's constructor.
    test<A> a;
    test<B> b;

    std::cout << "\nPress any key & enter to quit." << std::endl;
    char c;
    std::cin >> c;

    return 0;
}

With the appropriate headers the above as is should compile, build & run giving the output below (At least in MSVS 2017 on Windows 7 64bit did); I left comments where I ran into errors and tried multiple different techniques to achieve a working example, errors occurred as others suggested and I found even more while working with the above code. What I was able to compile, build and run came down to this simple bit of code here without the comments. I also added another simple class to show it will work with any type:

template<class>
class test {
public:
    test();
};

template<class T>
test<T>::test() {
    std::function<void( T )> fooCall = []( auto a ) { std::cout << a.sayHello(); };
    T t;
    fooCall( t );
}

struct A {
    std::string sayHello() { return "A say's Hello!\n"; }
};

struct B {
    std::string sayHello() { return "B say's Hello!\n"; }
};

struct C {    
    int sayHello() { return 100; }
};

int main() {
    test<A> testA;
    test<B> testB;
    test<C> testC;

    std::cout << "\nPress any key & enter to quit." << std::endl;
    char c;
    std::cin >> c;

    return 0;
}

Output:

A say's Hello!
B say's Hello!
100

Press any key & enter to quit

I don't know if this will help the OP directly or indirectly or not but if it does or even if it doesn't it is still something that they may come back to and build off of.