joe*_*ech 7 c++ plugins functional-programming variadic-templates c++17
我有一个框架,它有一个类型擦除的函数类型Function,将std::any实例映射到std::any实例和函数注册表std::unordered_map<std::string, Function>。该框架有一个插件系统,插件作者可以在此函数注册表中注册函数。这样,新功能可以在运行时集成到框架中。
该框架的一个非常精简的实现可能如下所示:
#include <any>
#include <vector>
#include <memory>
#include <functional>
#include <iostream>
class Function
{
public:
template <typename R, typename... Args>
Function(R(*f)(Args...)) : impl(std::make_unique<FunctionImpl<R, Args...>>(f)) {}
template <typename... Args>
std::any operator()(Args&&...args) const
{
std::vector<std::any> args_vec({std::forward<Args>(args)...});
return impl->invoke(args_vec);
}
private:
struct FunctionProxy {
virtual std::any invoke(std::vector<std::any>&) const = 0;
virtual ~FunctionProxy(){}
};
template <typename R, typename... Args>
class FunctionImpl : public FunctionProxy
{
public:
FunctionImpl(R(*f)(Args...)) : fun{f} {}
std::any invoke(std::vector<std::any>& args) const override
{
return invoke_impl(args, std::make_index_sequence<sizeof...(Args)>{});
}
private:
template <size_t... I>
std::any invoke_impl(std::vector<std::any>& args, std::index_sequence<I...>) const
{
if constexpr (std::is_void_v<R>) {
fun(std::any_cast<Args>(args[I])...);
return std::any{};
} else {
return std::any(fun(std::any_cast<Args>(args[I])...));
}
}
std::function<R(Args...)> fun;
};
std::unique_ptr<FunctionProxy> impl;
};
class FunctionRegistry
{
public:
template <typename R, typename... Args>
void register_function(R(*fun)(Args...), std::string name)
{
functions.emplace(std::make_pair(name, Function(fun)));
}
Function const& resolve(std::string const& name){
return functions.at(name);
}
private:
std::unordered_map<std::string, Function> functions;
};
/***********************************************************************************/
double add(double x, double y) {
return x+y;
}
template <typename... Args>
double sum(Args... args) {
return (0 + ... + args);
}
int main()
{
// code called in void init() function provided by plugin
FunctionRegistry functions;
functions.register_function(&add, "add");
//....
// code called in the framework at runtime
auto const& f = functions.resolve("add");
std::cout << std::any_cast<double>(f(5.,3.));
return 0;
}
Run Code Online (Sandbox Code Playgroud)
https://godbolt.org/z/aT4n36v6M
请注意,为了解决这个问题,示例被简化了(没有错误检查,真正的代码不使用std::any而是使用适当的反射库,它不限于函数指针和 std::function,主函数的第一部分将作为插件的一部分实现......)
该代码适用于非模板函数。但现在我想让用户能够在插件中提供可变参数函数。我意识到在当前设置中这是不可能的,因为模板是在调用站点初始化的,而调用站点没有编译为插件的一部分。
有什么方法可以允许用户在这种框架中注册可变参数函数吗?
我对所有解决方案持开放态度,只要现有功能保持不变,包括 API 在内的代码的任何部分都可以更改。
唯一的要求是,注册的函数是真正的可变参数:我知道我可以在插件中显式实例化 1,2,3,....,n 个参数的可变参数函数,并单独注册每个实例化。
正如问题中已经指出的,对于template<typename ...T> void for(T...);可变参数函数(或一般来说 - 任何函数模板),您可以注册它的任何实例 - 就像
fwk.register("foo_ifi", &foo<int,float,int>);
这就是它所能做的一切。除非问题是不提供模板参数 - 那么你可以使用 lambda 来包装你的函数模板:
template <typename ...T> auto foo(T... x) { .... }
std::function<void(int,int,int)> f =
[](auto ...x) ->decltype(auto) { return foo(x...); };
Run Code Online (Sandbox Code Playgroud)
固定数量参数的解决方案:
如果您可以将框架限制为某个最大参数数量和某个固定数量的类型 - 那么这是可行的 - 请参阅示例。
只有几个辅助类:
namespace detail
{
template <typename, typename>
struct FunctionBuilder;
template <std::size_t ...I, typename V>
struct FunctionBuilder<std::index_sequence<I...>, V>
{
using type = std::function<V(decltype(I,std::declval<V>())...)>;
};
template <typename, typename>
struct MapsBuilder;
template <std::size_t ...I, typename V>
struct MapsBuilder<std::index_sequence<I...>, V>
{
using type = std::tuple<
std::map<std::string,
typename FunctionBuilder<std::make_index_sequence<I>, V>::type>...>;
};
}
Run Code Online (Sandbox Code Playgroud)
框架本身 - 通过一些模板魔法、元组、变体、函数和 lambda - 它几乎可以自动完成:
template <std::size_t MaxArg, typename ...T>
struct Framework {
using Value = std::variant<T...>;
using Maps = detail::MapsBuilder<std::make_index_sequence<MaxArg>, Value>::type;
Maps maps;
template <typename R, typename ...A>
void registerFunction(std::string name, R (*fun)(A...))
{
std::get<sizeof...(A)>(maps)[std::move(name)] = [fun](auto ...x) -> Value {
return fun(std::get<A>(x)...);
};
}
template <std::size_t N, typename Callable>
void registerCallable(std::string name, Callable fun)
{
std::get<N>(maps)[std::move(name)] = [fun](auto ...x) -> Value {
return std::visit([&](auto ...a) -> Value { return fun(a...); }, x...);
};
}
template <typename Callable>
void registerVariadicCallable(std::string name, Callable fun)
{
auto funForValues = [fun](auto ...x) -> Value {
return std::visit([&](auto ...a) -> Value { return fun(a...); }, x...);
};
std::apply([&](auto& ...m) { ((m[name] = funForValues), ...); }, maps);
}
template <typename ...A>
Value call(std::string const& name, A ...a) {
return std::get<sizeof...(A)>(maps)[name](Value(a)...);
}
};
Run Code Online (Sandbox Code Playgroud)
以及它有效的证明:
template <typename ...T>
auto sum(T ...a) {
return (a + ... + 0);
}
int sum2(int a, int b)
{
return a + b;
}
int main() {
Framework<5, int, float> fwk;
fwk.registerFunction("sum2", sum2);
fwk.registerCallable<2>("sum2t", [](auto ...x){ return sum(x...); });
fwk.registerVariadicCallable("sum", [](auto ...x){ return sum(x...); });
auto r1 = std::get<int>(fwk.call("sum2", 1, 2));
auto r2 = std::get<float>(fwk.call("sum2t", 1, 2.1f));
auto r3 = std::get<float>(fwk.call("sum", 1, 2.1f, 3));
std::cout << r1 << '\n';
std::cout << r2 << '\n';
std::cout << r3 << '\n';
}
Run Code Online (Sandbox Code Playgroud)
任意数量参数的解决方案:
如果您想注册此函数模板本身,使其可调用任何参数 - 您需要使框架也是一个知道其“函数”的模板。像这样的东西
注意:所有这些都需要 C++20,但在 C++17 甚至 C++11 中也是可行的 - 但需要更多的努力。
template <auto NamedCallable, auto ...NamedCallables>
class Framework;
template <auto NamedCallable>
class Framework<NamedCallable> {
public:
template <typename ...T>
static decltype(auto) call(std::string_view name, T... params)
{
if (NamedCallable.name == name)
return NamedCallable(std::forward<T>(params)...);
throw std::runtime_error("Not found caller: " + std::string(name));
}
};
template <auto NamedCallable, auto ...NamedCallables>
class Framework : Framework <NamedCallables...> {
public:
template <typename ...T>
static decltype(auto) call(std::string_view name, T... params)
{
if (NamedCallable.name == name)
return NamedCallable(std::forward<T>(params)...);
return Framework<NamedCallables...>::call(name, std::forward<T>(params)...);
}
};
Run Code Online (Sandbox Code Playgroud)
为了使注册更容易一些 - 需要一些辅助类:
template<size_t N>
struct StringLiteral {
constexpr StringLiteral(const char (&str)[N]) {
std::copy_n(str, N, value);
}
constexpr operator std::string_view() const { return value; }
char value[N];
};
template <auto Name, auto Callable>
struct NamedCallable
{
static constexpr std::string_view name = Name;
template <typename ...T>
decltype(auto) operator()(T... a) const {
return Callable(std::forward<T>(a)...);
}
};
Run Code Online (Sandbox Code Playgroud)
一些例子 - 如何使用它:
// A variadic function
template <typename ...T>
void foo(T... a) {
std::cout << "foo:";
((std::cout << " " << a), ...);
std::cout << "\n";
}
// A regular function
void bar(int a, int b, int c) {
std::cout << "bar: " << a << ' ' << b << ' ' << c << '\n';
}
using MyFramework = Framework<
NamedCallable<StringLiteral{"foo"}, [](auto ...x) { return foo(x...); }>{},
NamedCallable<StringLiteral{"bar"}, bar>{}>;
Run Code Online (Sandbox Code Playgroud)
正如您所看到的 - 无论如何,这样的变量需要包含在 lambda 中 - 因为只有 lambda 是我们可以作为非模板事物提供的东西 - 然而它的调用运算符是一个方法模板。
以及它是如何工作的:
int main() {
MyFramework fwk;
fwk.call("foo", 1, 2, 3);
fwk.call("bar", 1, 2, 3);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
205 次 |
| 最近记录: |