是否可以将通用lambda作为非模板参数传递

luk*_*k32 13 c++ lambda generic-lambda c++14

我有一个玩具示例,我想在架构上进行修改以删除Processoron的类型依赖性EmitterT

#include <iostream>
#include <utility>

using namespace std;

struct Emitter {
    void e(int) { cout << "emitting int\n";}
    void e(double) { cout << "emitting double\n";}
    void e(char*) { cout << "emitting char*\n";}
    void e(const char*) { cout << "emitting const char*\n";}
};

template <typename EmitterT>
struct Processor {

    Processor(EmitterT e) : e_{e} {}

    template <typename T>
    void process(T&& value) {
        cout << "some processing... ";
        e_(std::forward<T>(value));
    }

    EmitterT e_;

};

template<typename Emitter_>
Processor<Emitter_> makeProcessor(Emitter_ e) { return Processor<Emitter_>(e);}

int main() {
    Emitter em;
    auto p = makeProcessor([&em](auto v){em.e(v);});


    p.process(1);
    p.process("lol");
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

动机

我想将负责利用处理结果的部分与处理本身分离。本Emitter类结构,是给我,所以我要支持重载函数。

我想将lambda函数传递给将使用该函数的处理器。有点像回调机制,但是它必须是通用lambda,以支持重载。

我尝试过的

我写的示例有效,但它取决于Emitter类型作为模板参数。我不喜欢Processor根据更改类型Emitter。它也具有传染性,我具有真正的Processor等级结构,并且Emitter传播程度const甚至更差。

在阅读/sf/answers/1206355461/之后,我尝试使用以下struct作为成员:

struct EmitterC {
    template<typename T>
    void operator()(T value) { }
};
Run Code Online (Sandbox Code Playgroud)

但是我无法找到一种将其用作常规参数Emitter后推迟执行的方法Processor。它具有前向声明和引用,EmitterC&但仅支持一个Emitter定义。我想出的唯一方法是删除lambda,并对EmitterC我期望的每种类型进行虚拟重载,Emitter并将其用作基类。

因此,有没有办法将(通用)lambda作为参数传递,从而使Processor类型不依赖于Emitter

我仅限于c ++ 14,但如果有更好的支持,我也对更现代的标准感兴趣。

Jus*_*tin 8

最简单的解决方案是为Emitter以下参数设置参数process

struct Processor {
    template <typename T, typename EmitterFn>
    void process(T&& value, EmitterFn emit) {
        cout << "some processing... ";
        emit(std::forward<T>(value));
    }

};
Run Code Online (Sandbox Code Playgroud)

但是,如果它必须是其中的一员Processor并且可以枚举可能的函数签名,则可以使用某种类型的擦除。std::function否则提出的建议std::function_ref将无法使用,因为它们只允许一个函数签名,但是我们可以编写自己的函数overloaded_function_ref

template <typename Derived, typename Sig>
class function_ref_impl;

template <typename Derived, typename R, typename... Args>
class function_ref_impl<Derived, R(Args...)> {
    using fn_t = R(*)(void const*, Args...);

public:
    auto operator()(Args... args) const -> R {
        return fn(static_cast<Derived const&>(*this).object, std::forward<Args>(args)...);
    }

protected:
    template <typename F,
        std::enable_if_t<!std::is_base_of<function_ref_impl, F>::value, int> = 0>
    explicit function_ref_impl(F const& f)
        : fn{[](void const* self, Args... args) -> R {
            return (*static_cast<F const*>(self))(std::forward<Args>(args)...);
        }}
    {}

private:
    fn_t fn;
};

template <typename... Sig>
class overloaded_function_ref
    : public function_ref_impl<overloaded_function_ref<Sig...>, Sig>...
{
public:
    template <typename F,
        std::enable_if_t<!std::is_base_of<overloaded_function_ref, F>::value, int> = 0>
    overloaded_function_ref(F const& f)
        : function_ref_impl<overloaded_function_ref, Sig>(f)...
        , object{std::addressof(f)}
    {}

    // Can be done pre-C++17, but it's not easy:
    using function_ref_impl<overloaded_function_ref, Sig>::operator()...;

    // This can be encapsulated with techniques such as the "passkey" idiom.
    // Variadic friend expansion isn't a thing (`friend bases...`).
    void const* object;
};
Run Code Online (Sandbox Code Playgroud)

现场例子

确实需要C ++ 17的using /* base */::operator()...,但可以在C ++ 14被仿真; 请参阅介绍此功能的论文:[P0195],或者可以按摩Boost HOFmatch来执行此操作。这也是一个函数引用,而不是一个拥有函数。

然后我们可以写:

struct Processor {
    template <typename T>
    void process(T&& value) {
        cout << "some processing... ";
        emit(std::forward<T>(value));
    }

    using emitter_t = overloaded_function_ref<
        void(int),
        void(double),
        void(char*),
        void(char const*)
    >;

    emitter_t emit;
};
Run Code Online (Sandbox Code Playgroud)

演示版


Mel*_*nez 7

恕我直言:继承是在这里。

#include <iostream>
#include <utility>

using namespace std;

struct BaseEmitter {
    virtual void e(int) =0;
    virtual void e(double)=0;
    virtual void e(char*)=0;
    virtual void e(const char*)=0;
};

struct Emitter :public BaseEmitter {
    virtual void e(int) { cout << "emitting int\n";}
    virtual void e(double) { cout << "emitting double\n";}
    virtual void e(char*) { cout << "emitting char*\n";}
    virtual void e(const char*) { cout << "emitting const char*\n";}
};

struct Processor {
    BaseEmitter& e_;
    Processor(BaseEmitter& e) : e_(e) {}

    template <typename T>
    void process(T&& value) {
        cout << "some processing... ";
        e_(std::forward<T>(value));
    }
};


int main() {
    Emitter em;
    auto p = Processor(em);
    p.process(1);
    p.process("lol");
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

您可以通过在接口中进行继承来混合以捕获lambda:

struct bypass
{
        virtual void operator()() = 0;
};

template<typename callable> struct capture: public bypass
{
        callable& _ref;
        capture(callable &ref)
        : _ref(ref)
        {;};

        virtual void operator()()
        {
                _ref();
        }
};

struct test
{
        bypass *_c;
        template<class T> test(T& callback)
        : _c(nullptr)
        {
          _c = new capture<decltype(callback)>(callback);
        };

        void doit()
        {
            (*_c)();
        }

};



int main(int argc, char* argv[])
{
        auto lambda = [](){std::cout << "hello\n";};
        test z=test(lambda);
        z.doit();
        return 0;
}
Run Code Online (Sandbox Code Playgroud)

  • 它得到了一些好评,因此对某些人一定有用! (2认同)

eer*_*ika 2

是否可以将通用 lambda 作为非模板参数传递

无法声明接受 lambda 作为参数的非模板函数。lambda 的类型是匿名的:它没有名称。不可能编写接受匿名类型参数的函数声明。

lambda 的类型是可以推导的,这就是为什么 lambda 可以传递到其参数类型被推导的函数模板中。

虽然这回答了问题,但它并没有提供解决方案。我认为解决方案不会那么简单。

  • @MaxLanghof您可以编写自己的接受多个签名的代码,只要您可以枚举可能的签名即可。例如 `overloaded_function_ref&lt;void(int), void(double), void(char*), void(char const*)&gt;` (2认同)