C++14:类成员为通用std::function的通用lambda函数

10

考虑以下伪代码片段:

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;
};

我希望的是一个类成员 fooCall,它存储一个通用的Lambda函数,并在构造函数中进行分配。但编译器会报告fooCall不能是一个模板数据成员。有没有简单的解决方案可以在类中存储通用Lambda函数?

2
很遗憾,这里没有简单的解决方案。将通用lambda视为类似于模板化函数;因为它尚未实例化,所以无法获取其地址。我们需要了解更多具体的用例。也许您可以通过一个小的工作流程来详细说明原始问题? - AndyG
你试图解决的实际问题是什么? - Barry
我无法将一个通用lambda存储为类的成员,但我可以在类的构造函数中使用它来调用其调用。 - Francis Cugler
3个回答

4

在运行时,你无法选择两个泛型lambda表达式之间的区别,因为你没有具体的签名来进行类型擦除。

如果你可以在编译时确定决策,你可以将类本身进行模板化:

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

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

您可以创建一个辅助函数来推断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>{});
}

谢谢,但是这个条件只能在运行时使用 :-( - Juergen
@juxeii:请更新原帖以反映这一点。我们需要进行某种类型擦除才能使其正常工作。 - AndyG

2

我无法直接将std::function<>作为通用lambda表达式存储在类中作为一个成员。但我能够在类的构造函数中具体地使用它。我不确定这是否是OP试图实现的目标,但这是我根据他们提供的代码所能编译、构建和运行的。

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;
}

使用适当的头文件,上述代码应该可以编译、构建和运行,并输出以下结果(至少在Windows 7 64位中使用MSVS 2017时是这样)。在遇到错误并尝试多种不同技术以实现工作示例时,我留下了注释。当他人建议时发生了错误,而我在处理上述代码时发现更多错误。我能够编译、构建和运行的内容归结为这个简单的代码片段,没有注释。我还添加了另一个简单的类来展示它适用于任何类型。
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;
}

输出:

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

Press any key & enter to quit

我不知道这是否会直接或间接地帮助到原帖作者,但如果确实有帮助,即使没有也是他们可以回来参考和建立的东西。


0

你可以简单地使用一个模板类或者...
如果你能够使用C++17,你可以将fooCall的类型设置为
std::function<void(const std::any&)>并且创建一个小包装器来执行它。

方法1:简单地使用一个模板类(C++14)。
方法2:似乎完全模仿了OP所想要的伪代码(C++17)。
方法3:比方法2更简单易用(C++17)。
方法4:允许我们改变fooCall的值(C++17)。

  • 演示所需的头文件和测试结构:
#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")); }
};
  • 方法一:
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;
}
  • 方法二:
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;
}

方法三:
/*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;
}

方法 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;
}
  • 任何方法的输出
Hello from A
Hello from B

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接