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

时间:2019-07-22 15:54:30

标签: c++ lambda c++14 generic-lambda

我有一个玩具示例,我想在结构上进行修改以删除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等级,并且Emitter的传播方式像const或更糟。

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

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

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

因此,是否有一种方法可以传递(通用)lambda作为参数,以使Emitter的类型不依赖于application/vnd.resolved+json

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

4 个答案:

答案 0 :(得分:5)

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

此{em>确实需要using /* base */::operator()...使用C ++ 17,但是可以在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

答案 1 :(得分:3)

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

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

答案 2 :(得分:1)

  

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

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

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

尽管这回答了问题,但没有提供解决方案。我认为解决方案不会很简单。

答案 3 :(得分:1)

如果您愿意付出高昂的运行成本以换取最小的约束,则可以将std::functionstd::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::anystd::function都拥有类型擦除的包装器。您可能为此分配了堆,或者可能适合其小型对象优化。您将拥有虚函数调用(或等效函数)。

Compiler Explorer link