我有一个玩具示例,我想在体系结构上进行修改以删除ProcessorEmitterT的类型依赖性:

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

动机

我想将负责利用处理结果的部分与处理本身分离。 Emitter类结构已提供给我,因此我必须支持重载函数。

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

我尝试过的

我编写的示例有效,但它取决于Emitter类型作为模板参数。我不喜欢Processor类型根据Emitter进行更改。它也具有传染性,我有一个真实的Processor层次结构和Emitterconst或更差的代码传播。

阅读https://stackoverflow.com/a/17233649/1133179之后,我尝试以以下struct身份作为成员玩:
struct EmitterC {
    template<typename T>
    void operator()(T value) { }
};

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

因此,是否有一种方法可以传递(通用)lambda作为参数,以使Processor类型不依赖于Emitter

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

最佳答案

最简单的解决方案是使Emitter成为process的参数:

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

};

但是,如果它必须是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;
};

Live example

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

然后我们可以写:
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;
};

Demo

09-28 12:15