与 std::function 类似,但具有更多不同的参数和 return 类型
Like std::function but with more varied argument and return types
我正在寻找一种方法来设置和调用具有任意参数和 return 类型的函数。一个用例是高级脚本。像这样:
// universal function
using dynfunction = std::any (*)(std::vector<std::any> args);
我做了一个简化的例子:
#include <any>
#include <vector>
#include <map>
#include <string>
#include <iostream>
using namespace std::string_literals;
using namespace std::string_view_literals;
class MyClass {
public:
int foo(double d, std::string m) {
std::cout << "foo " << d << ", " << m << std::endl;
return 42;
}
virtual double bar(int i) {
return -i;
}
};
class MyDerivedClass : public MyClass {
virtual double bar(int i) override {
return -i*3.1412;
}
};
void foobar(char c) {
std::cout << "foobar " << c << std::endl;
}
// universal function
using dynfunction = std::any (*)(std::vector<std::any> args);
// caller wrappers
std::any call_foo(std::vector<std::any> args) {
return std::any_cast<MyClass*>(args[0])->foo(std::any_cast<double>(args[1]), std::any_cast<std::string>(args[2]));
}
std::any call_bar(std::vector<std::any> args) {
return std::any_cast<MyClass*>(args[0])->bar(std::any_cast<int>(args[1]));
}
std::any call_foobar(std::vector<std::any> args) {
foobar(std::any_cast<char>(args[0]));
return {}; // void
}
// demonstrate dynamic resolution
std::map<const std::string_view, const dynfunction> functions = {
{ "foo"sv, call_foo },
{ "bar"sv, call_bar },
{ "foobar"sv, call_foobar }
};
int main() {
MyClass obj;
std::any ret = functions["foo"sv](std::vector<std::any>{&obj, 7.0, "Hello World!"s});
ret = functions["bar"sv](std::vector<std::any>{&obj, ret});
std::cout << "obj.bar returned " << std::any_cast<double>(ret) << std::endl;
MyDerivedClass obj2;
ret = functions["bar"sv](std::vector<std::any>{dynamic_cast<MyClass*>(&obj2), 11}); // derived class must be cast to base
std::cout << "obj2.bar returned " << std::any_cast<double>(ret) << std::endl;
functions["foobar"sv](std::vector<std::any>{'x'});
}
这可行,但我想知道是否有更简单或更直接的方法。
此外,困扰我的一件事是需要将多态类型,即派生 classes 的对象转换为正确的基础 class 才能工作(请参阅 obj2
这个例子)。有解决办法吗?
注意:该示例有意简化(使用 std::vector
作为参数,使用 std::map
进行查找,这些只是概念上的替代品)。
编辑:似乎需要更多信息。
这是“模型驱动架构”(MDA) 的一部分(不是 OMG 变体——我们的解决方案比它早了大约 6 年)。我们有我们自己的 OOP/4GL 语言“V”,我们在 1995 年创建了它。它在运行时保留所有元信息。这使我们能够动态生成所有 GUI、数据库设计、数据绑定、脚本接口。在其他语言中,这现在被称为“反射”,但与我们可以做的相比,Java&Co 中可用的内容非常有限。
MDA 意味着(除其他外)我们需要一种方法将控制从解决方案的动态“模型驱动”部分转移到实际应用程序逻辑,即功能。我们拥有的内置脚本语言只是众多用例中的一个,我认为它对于普通观众来说是最容易理解的。
原始数据类型的数量是有限的,而多态数据类型却有数千种。 std::any
似乎比 std::variant
更优雅,因为 a) 它有这种很酷但完全透明的堆优化,(即小数据类型不需要动态分配)和 b) 我们可以保持数据之间完整的关注点分离类型,即如果我们添加新的数据类型,所有 std::any
处理代码都可以保持不变。但如果std::variant
有重要的优势,我愿意考虑。
这是我的设计:
#include <any>
#include <cstddef>
#include <cstdio>
#include <functional>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
class Dyn_fun final {
public:
using arg_type = std::vector<std::any>;
using return_type = std::any;
Dyn_fun() = default;
template <typename Ret, typename... Args>
explicit Dyn_fun(Ret (*fp)(Args...)) : m_dispatcher{make_dispatcher(fp)} {}
template <typename Ret, typename... Args>
Dyn_fun& operator=(Ret (*fp)(Args...)) {
set_function(fp);
return *this;
}
template <typename Ret, typename... Args>
void set_function(Ret (*fp)(Args...)) {
m_dispatcher = make_dispatcher(fp);
}
explicit operator bool() const noexcept {
return static_cast<bool>(m_dispatcher);
}
bool operator==(std::nullptr_t) const noexcept {
return m_dispatcher == nullptr;
}
bool operator!=(std::nullptr_t) const noexcept { return !(*this == nullptr); }
return_type operator()(arg_type const& args) { return m_dispatcher(args); }
private:
using dispatcher_type = std::function<return_type(arg_type const&)>;
template <typename Ret, typename... Args>
static auto make_dispatcher(Ret (*fp)(Args...)) {
return make_dispatcher(fp, std::make_index_sequence<sizeof...(Args)>{});
}
// Extracts type at a specific index of argument pack
template <size_t index, typename... Args>
using type_at = std::decay_t<decltype(std::get<index>(
std::declval<std::tuple<Args...>>()))>;
template <typename Ret, typename... Args, size_t... Indices>
static auto make_dispatcher(Ret (*fun)(Args...),
std::index_sequence<Indices...>) {
return [fun](arg_type const& args) {
if (args.size() != sizeof...(Args))
throw std::runtime_error{"Argument count does not match"};
// this avoids std::any{ void }
if constexpr (!std::is_void_v<Ret>) {
return std::any(
fun(std::any_cast<type_at<Indices, Args...>>(args[Indices])...));
} else {
fun(std::any_cast<type_at<Indices, Args...>>(args[Indices])...);
return std::any{};
}
};
}
dispatcher_type m_dispatcher;
};
int foo(int i, char const* s, double d) {
return std::printf("foo: %d, %s, %.2f\n", i, s, d);
}
void print_sum(int a, int b) {
std::printf("print_sum: %d + %d = %d\n", a, b, a + b);
}
int main() {
// Construct with foo
Dyn_fun dyn_fun(foo);
Dyn_fun::arg_type args{10, "Hello", 4.2};
// Call with arguments
dyn_fun(args);
// Change the wrapped function
dyn_fun = print_sum;
// with different arguments
args.assign({10, 20});
// Call the new function
dyn_fun(args);
}
亮点:
- class 执行所有样板转换和调用包装函数。
- 可以动态分配任何功能。
我正在寻找一种方法来设置和调用具有任意参数和 return 类型的函数。一个用例是高级脚本。像这样:
// universal function
using dynfunction = std::any (*)(std::vector<std::any> args);
我做了一个简化的例子:
#include <any>
#include <vector>
#include <map>
#include <string>
#include <iostream>
using namespace std::string_literals;
using namespace std::string_view_literals;
class MyClass {
public:
int foo(double d, std::string m) {
std::cout << "foo " << d << ", " << m << std::endl;
return 42;
}
virtual double bar(int i) {
return -i;
}
};
class MyDerivedClass : public MyClass {
virtual double bar(int i) override {
return -i*3.1412;
}
};
void foobar(char c) {
std::cout << "foobar " << c << std::endl;
}
// universal function
using dynfunction = std::any (*)(std::vector<std::any> args);
// caller wrappers
std::any call_foo(std::vector<std::any> args) {
return std::any_cast<MyClass*>(args[0])->foo(std::any_cast<double>(args[1]), std::any_cast<std::string>(args[2]));
}
std::any call_bar(std::vector<std::any> args) {
return std::any_cast<MyClass*>(args[0])->bar(std::any_cast<int>(args[1]));
}
std::any call_foobar(std::vector<std::any> args) {
foobar(std::any_cast<char>(args[0]));
return {}; // void
}
// demonstrate dynamic resolution
std::map<const std::string_view, const dynfunction> functions = {
{ "foo"sv, call_foo },
{ "bar"sv, call_bar },
{ "foobar"sv, call_foobar }
};
int main() {
MyClass obj;
std::any ret = functions["foo"sv](std::vector<std::any>{&obj, 7.0, "Hello World!"s});
ret = functions["bar"sv](std::vector<std::any>{&obj, ret});
std::cout << "obj.bar returned " << std::any_cast<double>(ret) << std::endl;
MyDerivedClass obj2;
ret = functions["bar"sv](std::vector<std::any>{dynamic_cast<MyClass*>(&obj2), 11}); // derived class must be cast to base
std::cout << "obj2.bar returned " << std::any_cast<double>(ret) << std::endl;
functions["foobar"sv](std::vector<std::any>{'x'});
}
这可行,但我想知道是否有更简单或更直接的方法。
此外,困扰我的一件事是需要将多态类型,即派生 classes 的对象转换为正确的基础 class 才能工作(请参阅 obj2
这个例子)。有解决办法吗?
注意:该示例有意简化(使用 std::vector
作为参数,使用 std::map
进行查找,这些只是概念上的替代品)。
编辑:似乎需要更多信息。
这是“模型驱动架构”(MDA) 的一部分(不是 OMG 变体——我们的解决方案比它早了大约 6 年)。我们有我们自己的 OOP/4GL 语言“V”,我们在 1995 年创建了它。它在运行时保留所有元信息。这使我们能够动态生成所有 GUI、数据库设计、数据绑定、脚本接口。在其他语言中,这现在被称为“反射”,但与我们可以做的相比,Java&Co 中可用的内容非常有限。
MDA 意味着(除其他外)我们需要一种方法将控制从解决方案的动态“模型驱动”部分转移到实际应用程序逻辑,即功能。我们拥有的内置脚本语言只是众多用例中的一个,我认为它对于普通观众来说是最容易理解的。
原始数据类型的数量是有限的,而多态数据类型却有数千种。 std::any
似乎比 std::variant
更优雅,因为 a) 它有这种很酷但完全透明的堆优化,(即小数据类型不需要动态分配)和 b) 我们可以保持数据之间完整的关注点分离类型,即如果我们添加新的数据类型,所有 std::any
处理代码都可以保持不变。但如果std::variant
有重要的优势,我愿意考虑。
这是我的设计:
#include <any>
#include <cstddef>
#include <cstdio>
#include <functional>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
class Dyn_fun final {
public:
using arg_type = std::vector<std::any>;
using return_type = std::any;
Dyn_fun() = default;
template <typename Ret, typename... Args>
explicit Dyn_fun(Ret (*fp)(Args...)) : m_dispatcher{make_dispatcher(fp)} {}
template <typename Ret, typename... Args>
Dyn_fun& operator=(Ret (*fp)(Args...)) {
set_function(fp);
return *this;
}
template <typename Ret, typename... Args>
void set_function(Ret (*fp)(Args...)) {
m_dispatcher = make_dispatcher(fp);
}
explicit operator bool() const noexcept {
return static_cast<bool>(m_dispatcher);
}
bool operator==(std::nullptr_t) const noexcept {
return m_dispatcher == nullptr;
}
bool operator!=(std::nullptr_t) const noexcept { return !(*this == nullptr); }
return_type operator()(arg_type const& args) { return m_dispatcher(args); }
private:
using dispatcher_type = std::function<return_type(arg_type const&)>;
template <typename Ret, typename... Args>
static auto make_dispatcher(Ret (*fp)(Args...)) {
return make_dispatcher(fp, std::make_index_sequence<sizeof...(Args)>{});
}
// Extracts type at a specific index of argument pack
template <size_t index, typename... Args>
using type_at = std::decay_t<decltype(std::get<index>(
std::declval<std::tuple<Args...>>()))>;
template <typename Ret, typename... Args, size_t... Indices>
static auto make_dispatcher(Ret (*fun)(Args...),
std::index_sequence<Indices...>) {
return [fun](arg_type const& args) {
if (args.size() != sizeof...(Args))
throw std::runtime_error{"Argument count does not match"};
// this avoids std::any{ void }
if constexpr (!std::is_void_v<Ret>) {
return std::any(
fun(std::any_cast<type_at<Indices, Args...>>(args[Indices])...));
} else {
fun(std::any_cast<type_at<Indices, Args...>>(args[Indices])...);
return std::any{};
}
};
}
dispatcher_type m_dispatcher;
};
int foo(int i, char const* s, double d) {
return std::printf("foo: %d, %s, %.2f\n", i, s, d);
}
void print_sum(int a, int b) {
std::printf("print_sum: %d + %d = %d\n", a, b, a + b);
}
int main() {
// Construct with foo
Dyn_fun dyn_fun(foo);
Dyn_fun::arg_type args{10, "Hello", 4.2};
// Call with arguments
dyn_fun(args);
// Change the wrapped function
dyn_fun = print_sum;
// with different arguments
args.assign({10, 20});
// Call the new function
dyn_fun(args);
}
亮点:
- class 执行所有样板转换和调用包装函数。
- 可以动态分配任何功能。