我有一个玩具示例,我想在结构上进行修改以删除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) { }
};
但是当将Emitter
用作普通参数时,我无法找到一种方法来推迟在Processor
之后执行EmitterC&
。它使用前向声明和引用EmitterC
解决了问题,但仅支持一个Emitter定义。我想出的唯一方法是删除lambda,并在Emitter
中为我在Processor
中期望的每种类型进行虚拟重载,并将其用作基类。
因此,是否有一种方法可以传递(通用)lambda作为参数,以使Emitter
的类型不依赖于application/vnd.resolved+json
?
我仅限于c ++ 14,但如果有更好的支持,我也对更现代的标准感兴趣。
答案 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;
};
此{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;
};
答案 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::function
与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
都拥有类型擦除的包装器。您可能为此分配了堆,或者可能适合其小型对象优化。您将拥有虚函数调用(或等效函数)。