C++14: Generic lambda with generic std::function as class member
Asked Answered
D

3

10

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?

Downcast answered 16/11, 2017 at 14:32 Comment(3)
Unfortunately there is no simple solution. Consider a generic lambda to be like a templatized function; you cannot get its address because it hasn't been instantiated yet. We will need to know more about concrete use cases. Perhaps you can flesh out the OP with a small workflow?Informer
What's the actual problem you're trying to solve?Hirza
I wasn't able to store a generic lambda as a member of a class, but I was able to use one within a class's constructor to invoke its call.Knife
M
4

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>{});
}
Mayfield answered 16/11, 2017 at 14:41 Comment(2)
Thx, but the condition is only available at runtime :-(Downcast
@juxeii: Please update the OP to reflect that. We will need to do some form of type erasure to make it work.Informer
K
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.

Knife answered 18/11, 2017 at 9:11 Comment(0)
E
0

you can simply use a template class or...
If you can get away with using c++17, you could make fooCall's type
std::function<void(const std::any&)> and make a small wrapper for executing it.

method 1 : simply use a template class (C++14).
method 2 : seems to mimic the pseudo code exactly as the OP intended (C++17).
method 3 : is a bit simpler and easier to use than method 2 (C++17).
method 4 : allows us to change the value of fooCall (C++17).

  • required headers and test structures for the demo :
#include <any> //not required for method 1
#include <string>
#include <utility>
#include <iostream>
#include <functional>

struct typeA {
    constexpr const char * sayHello() const { return "Hello from A\n"; }
};

struct typeB {
    const std::string sayHello() const { return std::string(std::move("Hello from B\n")); }
};
  • method 1 :
template <typename T>
class C {
    const std::function<void(const T&)> fooCall;
public:
    C(): fooCall(std::move([](const T &a) { std::cout << a.sayHello(); })){}

    void execFooCall(const T &arg) {
        fooCall(arg);
    }
};

int main (void) {
    typeA A;
    typeB B;
    C<typeA> c1;
    C<typeB> c2;
    c1.execFooCall(A);
    c2.execFooCall(B);
    return 0;
}
  • method 2 :
bool is_true = true;

class C {
    std::function<void(const std::any&)> fooCall;
public:
    C() {
        if (is_true)
            fooCall = [](const std::any &a) { std::cout << std::any_cast<typeA>(a).sayHello(); };
        else
            fooCall = [](const std::any &a) { std::cout << std::any_cast<typeB>(a).sayHello(); };
    }
    template <typename T>
    void execFooCall(const T &arg) {
        fooCall(std::make_any<const T&>(arg));
    }
};

int main (void) {
    typeA A;
    typeB B;
    C c1;
    is_true = false;
    C c2;
    c1.execFooCall(A);
    c2.execFooCall(B);
    return 0;
}
  • method 3 :
/*Note that this very closely resembles method 1. However, we're going to 
  build off of this method for method 4 using std::any*/
template <typename T>
class C {
    const std::function<void(const std::any&)> fooCall;
public:
    C() : fooCall(std::move([](const std::any &a) { std::cout << std::any_cast<T>(a).sayHello(); })) {}

    void execFooCall(const T &arg) {
        fooCall(std::make_any<const T&>(arg));
    }
};

int main (void) {
    typeA A;
    typeB B;
    C<typeA> c1;
    C<typeB> c2;
    c1.execFooCall(A);
    c2.execFooCall(B);
    return 0;
}
  • method 4 :
/*by setting fooCall outside of the constructor we can make C a regular class 
  instead of a templated one, this also complies with the rule of zero.
  Now, we can change the value of fooCall whenever we want.
  This will also allow us to do things like create a container that stores
  a vector or map of functions that each take different parameter types*/
class C {
    std::function<void(const std::any&)> fooCall; //could easily be replaced by a vector or map
public:
    /*could easily adapt this to take a function as a parameter so we can change
      the entire body of the function*/
    template<typename T>
    void setFooCall() {
        fooCall = [](const std::any &a) { std::cout << std::any_cast<T>(a).sayHello(); };
    }

    template <typename T>
    void execFooCall(const T &arg) {
            fooCall(std::make_any<const T&>(arg));
    }
};

int main (void) {
    typeA A;
    typeB B;
    C c;
    c.setFooCall<typeA>;
    c.execFooCall(A);
    c.setFooCall<typeB>;
    c.execFooCall(B);
    return 0;
}
  • Output from Any method
Hello from A
Hello from B
Eighteenmo answered 4/3, 2021 at 23:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.