C++14:以通用 std::function 作为类成员的通用 lambda

Posted

技术标签:

【中文标题】C++14:以通用 std::function 作为类成员的通用 lambda【英文标题】:C++14: Generic lambda with generic std::function as class member 【发布时间】:2018-04-30 03:32:43 【问题描述】:

考虑这个伪sn-p:

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 有什么简单的解决方案吗?

【问题讨论】:

不幸的是,没有简单的解决方案。将通用 lambda 视为模板化函数;你不能得到它的地址,因为它还没有被实例化。我们需要更多地了解具体的用例。也许你可以用一个小的工作流程来充实 OP? 您要解决的实际问题是什么? 我无法将通用 lambda 存储为类的成员,但我能够在类的构造函数中使用一个来调用它。 【参考方案1】:

您无法在运行时在两个通用 lambda 之间进行选择,因为您没有具体的类型擦除签名。

如果您可以在编译时做出决定,您可以模板化类本身:

template <typename F>
class SomeClass

private:
    F fooCall;

public:
    SomeClass(F&& f) : fooCallstd::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>);

【讨论】:

谢谢,但该条件仅在运行时可用:-( @juxeii:请更新 OP 以反映这一点。我们需要进行某种形式的类型擦除才能使其正常工作。【参考方案2】:

我无法将std::function&lt;&gt; 作为generic lambda 直接作为member 在课堂上存储。我能够做的是在类的构造函数中专门使用一个。我不能 100% 确定这是否是 OP 试图实现的目标,但这是我能够使用我怀疑 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 中);我在遇到错误的地方离开了 cmets,并尝试了多种不同的技术来实现一个工作示例,正如其他人所建议的那样发生了错误,并且在使用上述代码时我发现了更多错误。我能够在没有 cmets 的情况下编译、构建和运行这些简单的代码。我还添加了另一个简单的类来展示它适用于任何类型:

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

我不知道这是否会直接或间接地帮助 OP,但如果它有帮助,或者即使没有,它仍然是他们可能会回来并以此为基础的东西。

【讨论】:

【参考方案3】:

您可以简单地使用模板类或... 如果您可以使用 c++17,您可以制作 fooCall 的类型std::function&lt;void(const std::any&amp;)&gt; 并制作一个小包装器来执行它。

方法 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;

方法四:
/*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

【讨论】:

以上是关于C++14:以通用 std::function 作为类成员的通用 lambda的主要内容,如果未能解决你的问题,请参考以下文章

C11新特性之std::function与std::bind

如何创建一个可变的通用lambda?

c++11之functionbind

C++11新特性 :函数对象包装器

C++ 中std::function各种使用方法和例程

Lambda 捕获 'this' 保存为 std::function