13

我有一个玩具示例,我想在架构上进行修改以删除 on 的类型Processor依赖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;
}

动机

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

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

我试过的:

我编写的示例有效,但它取决于Emitter作为模板参数的类型。我不喜欢Processor基于Emitter. 它也具有传染性,我有一个真正的Processor等级制度,并且Emitter传播得差不多const或更糟。

阅读https://stackoverflow.com/a/17233649/1133179后,我尝试使用以下结构作为成员:

struct EmitterC {
    template<typename T>
    void operator()(T value) { }
};

但是当将其用作普通参数时,我无法找到一种推迟执行Emitterafter的方法。Processor它使用前向声明和引用来解决,EmitterC&但它只支持一个发射器定义。我能想出的唯一方法是删除 lambda,并EmitterC为我期望的每种类型进行虚拟重载Emitter并将其用作基类。

那么,有没有办法将(通用)lambda 作为参数传递,以便该Processor类型不依赖于Emitter

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

4

4 回答 4

8

这个最简单的解决方案是将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;
};

活生生的例子

确实需要 C++17 using /* base */::operator()...,但可以在 C++14 中模拟;请参阅介绍此功能的论文:[P0195],或者也许可以按摩Boost HOF来做到这一点。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;
};

演示

于 2019-07-22T16:51:25.587 回答
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;
}

您可以进行混合以捕获 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);
        };
        virtual ~test()
        {
            delete _c;
        };
        void doit()
        {
            (*_c)();
        }

};



int main(int argc, char* argv[])
{
        auto lambda = [](){std::cout << "hello\n";};
        test z=test(lambda);
        z.doit();
        return 0;
}
于 2019-07-22T17:48:55.950 回答
3

如果您愿意付出高昂的运行时成本以换取最小的约束,您可以使用std::functionwith std::any(对于 C++14,使用boost::any):

#include <iostream>
#include <utility>
#include <any>
#include <functional>

struct Processor {
    Processor(std::function<void(std::any)> e) : e_{e} {}

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

    std::function<void(std::any)> e_;
};

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

int main() {
    Emitter em;
    auto p = Processor(
        [&em](std::any any){
            // This if-else chain isn't that cheap, but it's about the best
            // we can do. Alternatives include:
            // - Hashmap from `std::type_index` (possibly using a perfect hash)
            //   to a function pointer that implements this.
            // - Custom `any` implementation which allows "visitation":
            //
            //   any.visit<int, double, char*, char const*>([&em] (auto it) {
            //        em.e(it);
            //   });
            if (auto* i = std::any_cast<int>(&any)) {
                em.e(*i);
            } else if (auto* d = std::any_cast<double>(&any)) {
                em.e(*d);
            } else if (auto* cstr = std::any_cast<char*>(&any)) {
                em.e(*cstr);
            } else {
                em.e(std::any_cast<char const*>(any));
            }
        }
    );


    p.process(1);
    p.process("lol");
    return 0;
}

std::any并且std::function都拥有类型擦除的包装器。您可能为此进行了堆分配,或者您可能适合他们的小对象优化。您将有虚函数调用(或等效函数)。

编译器资源管理器链接

于 2019-07-22T17:07:43.863 回答
2

Is it possible to pass generic lambda as non-template argument

It is not possible to declare a non-template function that accepts a lambda as an argument. The type of a lambda is anonymous: It has no name. It is not possible to write a function declaration that accepts an argument of an anonymous type.

The type of the lambda can be deduced, which is why lambdas can be passed into function templates whose argument types are deduced.

While this answers the question, it does not offer a solution. I don't think a solution is going to be simple.

于 2019-07-22T16:10:11.437 回答