C++ 17向量的通用(多态)lambdas

inf*_*oop 8 c++ c++17

C++ 14引入了通用lambdas(在lambda签名中使用auto关键字时).

有没有办法将它们存储在带有C++ 17的向量中?

我知道这个现有的问题,但它不适合我的需要:我可以有一个模板函数指针的std :: vector吗?

这是一个示例代码,说明了我想要做的事情.(请在回答前查看底部的注释)

#include <functional>
#include <vector>

struct A {
    void doSomething() {
        printf("A::doSomething()\n");
    }
    void doSomethingElse() {
        printf("A::doSomethingElse()\n");
    }
};

struct B {
    void doSomething() {
        printf("B::doSomething()\n");
    }
    void doSomethingElse() {
        printf("B::doSomethingElse()\n");
    }
};

struct TestRunner {
    static void run(auto &actions) {
        A a;
        for (auto &action : actions) action(a);
        B b;
        for (auto &action : actions) action(b); // I would like to do it
        // C c; ...
    }
};

void testCase1() {
    std::vector<std::function<void(A&)>> actions; // Here should be something generic instead of A
    actions.emplace_back([](auto &x) {
        x.doSomething();
    });
    actions.emplace_back([](auto &x) {
        x.doSomethingElse();
    });
    // actions.emplace_back(...) ...
    TestRunner::run(actions);
}

void testCase2() {
    std::vector<std::function<void(A&)>> actions; // Here should be something generic instead of A
    actions.emplace_back([](auto &x) {
        x.doSomething();
        x.doSomethingElse();
    });
    actions.emplace_back([](auto &x) {
        x.doSomethingElse();
        x.doSomething();
    });
    // actions.emplace_back(...) ...
    TestRunner::run(actions);
}

// ... more test cases : possibly thousands of them
// => we cannot ennumerate them all (in order to use a variant type for the actions signatures for example)

int main() {
    testCase1();
    testCase2();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

注意:

  • 代码A,B并且TestRunner不能更改,只有测试用例的代码
  • 我不想讨论这样的代码测试是好还是坏,这是偏离主题的(这里使用的测试术语只是为了说明我不能枚举所有lambdas(为了使用变量类型) ...))

sky*_*ack 5

它遵循一个可能的解决方案(我不建议,但你明确表示你不想讨论它是好还是坏等等).
按照要求,A,B并TestRunner没有改变(抛开事实,auto不是一个有效的函数参数TestRunner,我把它相应的).
如果你可以略微改变TestRunner,整个事情都可以改进.
话虽这么说,这是代码:

#include <functional>
#include <vector>
#include <iostream>
#include <utility>
#include <memory>
#include <type_traits>

struct A {
    void doSomething() {
        std::cout << "A::doSomething()" << std::endl;
    }
    void doSomethingElse() {
        std::cout << "A::doSomethingElse()" << std::endl;
    }
};

struct B {
    void doSomething() {
        std::cout << "B::doSomething()" << std::endl;
    }
    void doSomethingElse() {
        std::cout << "B::doSomethingElse()" << std::endl;
    }
};

struct Base {
    virtual void operator()(A &) = 0;
    virtual void operator()(B &) = 0;
};

template<typename L>
struct Wrapper: Base, L {
    Wrapper(L &&l): L{std::forward<L>(l)} {}

    void operator()(A &a) { L::operator()(a); }
    void operator()(B &b) { L::operator()(b); }
};

struct TestRunner {
    static void run(std::vector<std::reference_wrapper<Base>> &actions) {
        A a;
        for (auto &action : actions) action(a);
        B b;
        for (auto &action : actions) action(b);
    }
};

void testCase1() {
    auto l1 = [](auto &x) { x.doSomething(); };
    auto l2 = [](auto &x) { x.doSomethingElse(); };

    auto w1 = Wrapper<decltype(l1)>{std::move(l1)};
    auto w2 = Wrapper<decltype(l2)>{std::move(l2)};

    std::vector<std::reference_wrapper<Base>> actions;
    actions.push_back(std::ref(static_cast<Base &>(w1)));
    actions.push_back(std::ref(static_cast<Base &>(w2)));

    TestRunner::run(actions);
}

void testCase2() {
    auto l1 = [](auto &x) {
        x.doSomething();
        x.doSomethingElse();
    };

    auto l2 = [](auto &x) {
        x.doSomethingElse();
        x.doSomething();
    };

    auto w1 = Wrapper<decltype(l1)>{std::move(l1)};
    auto w2 = Wrapper<decltype(l2)>{std::move(l2)};

    std::vector<std::reference_wrapper<Base>> actions;
    actions.push_back(std::ref(static_cast<Base &>(w1)));
    actions.push_back(std::ref(static_cast<Base &>(w2)));

    TestRunner::run(actions);
}

int main() {
    testCase1();
    testCase2();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我无法看到在矢量中存储非齐次lambda的方法,因为它们只是非均匀类型.
无论如何,通过定义接口(请参阅参考资料Base)并使用Wrapper继承自给定接口和lambda 的模板类(请参阅参考资料),我们可以将请求转发给给定的通用lambda,并且仍然具有同构接口.
换句话说,解决方案的关键部分是以下类:

struct Base {
    virtual void operator()(A &) = 0;
    virtual void operator()(B &) = 0;
};

template<typename L>
struct Wrapper: Base, L {
    Wrapper(L &&l): L{std::forward<L>(l)} {}

    void operator()(A &a) { L::operator()(a); }
    void operator()(B &b) { L::operator()(b); }
};
Run Code Online (Sandbox Code Playgroud)

可以从lambda创建包装器,如下所示:

auto l1 = [](auto &) { /* ... */ };
auto w1 = Wrapper<decltype(l1)>{std::move(l1)};
Run Code Online (Sandbox Code Playgroud)

不幸的是,由于要求是不修改TestRunner,我必须使用std::ref并std::reference_wrapper能够在向量中放置引用.

在wandbox上看到它.