我用 C++ & boost 编写了一个程序。是否可以编写一个模板类,从具有未知数量参数的函数中生成仿函数,例如my_call<func>(vector<variant>)
fun 可以在哪里bool fun(string)
或bool fun(int, int, string)
等?
1 回答
首先,重要的是要认识到这boost::variant<>
是一个类模板,它需要它可以包含的所有可能类型的列表。所以,你不会只有一个vector<variant>
,而是一个vector<variant<string, double>>
,或vector<variant<int, double, string, my_class>>
,你将无法混合它们。
这让我觉得你可能想要使用boost::any
而不是boost::variant<>
. 因此,我在这里提出了一个可以使用boost::variant
并且可以稍微修改以使用的解决方案boost::any
,因此您可以选择您喜欢的版本。
首先,我必须承认该解决方案使用起来很简单,但理解起来并不那么简单,所以我必须先介绍一些机器。这种机制对于基于变体和基于任意的解决方案都是通用的。
//=============================================================================
// META-FUNCTIONS FOR CREATING INDEX LISTS
// The structure that encapsulates index lists
template <size_t... Is>
struct index_list
{
};
// Collects internal details for generating index ranges [MIN, MAX)
namespace detail
{
// Declare primary template for index range builder
template <size_t MIN, size_t N, size_t... Is>
struct range_builder;
// Base step
template <size_t MIN, size_t... Is>
struct range_builder<MIN, MIN, Is...>
{
typedef index_list<Is...> type;
};
// Induction step
template <size_t MIN, size_t N, size_t... Is>
struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
{
};
}
// Meta-function that returns a [MIN, MAX) index range
template<size_t MIN, size_t MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;
元类index_range
允许定义整数的编译时序列。Jonathan Wakely提出了一个有趣的建议来标准化这种结构,这样整个机器就不需要了。然而,就目前而言,我们必须像上面那样手动编写代码。
现在我们可以构建编译时整数序列,我们可以利用可变参数模板和参数解包来创建一个调度机制,将variant
参数向量转换为常规参数列表。请注意具体variant<>
类型必须如何作为模板参数提供。基于. _ _any
// Headers needed for the implementation of the dispatcher
#include <vector>
#include <functional>
#include <boost/variant.hpp>
// Just for convenience
using namespace std;
using boost::variant;
//============================================================================
// DISPATCHER IMPLEMENTATION
// Call dispatching mechanism: notice how the underlying variant type
// must be provided as a template argument (the first one)
template<typename VT, typename R, typename... Args>
struct dispatcher
{
template<typename F>
dispatcher(F f) : _f(f) { }
// The call operator which performs the variant dispatch
R operator () (vector<VT> const& v)
{
if (v.size() != sizeof...(Args))
{
// Wrong number of arguments provided!
return false;
}
// Delegates to internal function call: needed for deducing
// a sequence of integers to be used for unpacking.
index_range<0, sizeof...(Args)> indexes;
return do_call(v, indexes);
}
private:
// The heart of the dispatching mechanism
template<size_t... Is>
R do_call(vector<VT> const& v, index_list<Is...> indexes)
{
return _f((get_ith<Args>(v, Is))...);
}
// Helper function that extracts a typed value from the variant.
template<typename T>
T get_ith(vector<VT> const& v, size_t i)
{
return boost::get<T>(v[i]);
}
// Wrapper that holds the function to be invoked.
function<R(Args...)> _f;
};
// Helper function that allows deducing the input function signature
template<typename VT, typename R, typename... Args>
function<R (vector<VT> const&)> get_dispatcher(R (*f)(Args...))
{
dispatcher<VT, R, Args...> d(f);
return d;
}
最后,简短演示一下如何使用它。假设我们有两个测试函数,如下所示:
#include <iostream>
bool test1(string s, double d)
{
cout << s << " " << d << endl;
return true;
}
bool test2(int i1, int i2, string s1, string s2)
{
cout << i1 << " " << i2 << " " << s1 << " " << s2 << endl;
return true;
}
我们想要的是通过构建一个变体向量来调用它们并将其分派到所需的函数。我必须再次强调一个事实,即我们需要指定我们的变体可以包含的所有类型的列表。在这里,我假设这些类型是string
、double
和int
,但您的程序可能会使用不同的类型。
此外,该解决方案基于std::function<>
实现类型擦除,允许您创建不同类型的函子并统一调用它们。因此,还提供了一个方便的类型定义std::function<>
(这又取决于variant<>
我们使用的类型):
int main()
{
// A helper type definition for the variant
typedef variant<int, double, string> vt;
// A helper type definition for the function wrapper
typedef function<bool (vector<vt>)> dispatcher_type;
// Get a caller for the first function
dispatcher_type f1 = get_dispatcher<vt>(test1);
// Prepare arguments for the first function
vector<vt> v = {"hello", 3.14};
// Invoke the first function
f1(v);
// Get a caller for the second function
dispatcher_type f2 = get_dispatcher<vt>(test2);
// Prepare arguments for the second function
v.assign({1, 42, "hello", "world"});
// Invoke the second function
f2(v);
}
由于所有调度程序都有类型dispatcher_type
,因此您可以轻松地将它们放入容器中。但是,您必须注意这样一个事实,即尝试使用错误数量的参数调用函数只会在运行时std::vector<>
被检测到(在编译时不可能知道一个包含多少元素)。因此,必须采取适当的措施。
正如所承诺的,我现在将稍微修改此解决方案以使用boost::any
而不是boost::variant
. 优点是由于boost::any
可以保存任何值,因此无需指定可用作函数参数的可能类型列表。
虽然辅助机制没有改变,但核心调度程序类模板必须修改如下:
#include <vector>
#include <functional>
#include <boost/any.hpp>
using namespace std;
using boost::any;
//=============================================================================
// DISPATCHER IMPLEMENTATION
template<typename R, typename... Args>
struct dispatcher
{
template<typename F>
dispatcher(F f) : _f(f) { }
// The call operator which performs the dispatch
R operator () (vector<any> const& v)
{
if (v.size() != sizeof...(Args))
{
// Wrong number of arguments provided!
return false;
}
// Delegates to internal function call: needed for deducing
// a sequence of integers to be used for unpacking.
index_range<0, sizeof...(Args)> indexes;
return do_call(v, indexes);
}
private:
// The heart of the dispatching mechanism
template<size_t... Is>
R do_call(vector<any> const& v, index_list<Is...> indexes)
{
return _f((get_ith<Args>(v, Is))...);
}
// Helper function that extracts a typed value from the variant.
template<typename T>
T get_ith(vector<any> const& v, size_t i)
{
return boost::any_cast<T>(v[i]);
}
// Wrapper that holds the function to be invoked.
function<R(Args...)> _f;
};
// Helper function
template<typename R, typename... Args>
function<R (vector<any> const&)> get_dispatcher(R (*f)(Args...))
{
dispatcher<R, Args...> d(f);
return d;
}
如您所见,VT
模板参数已经消失。特别是,可以在get_dispatcher
不显式指定任何模板参数的情况下调用。使用我们为基于 - 的解决方案定义的相同测试函数variant
,您将如何调整main()
例程:
int main()
{
// Helper type definition
typedef function<bool (vector<any>)> dispatcher_type;
// Get a caller for the first function
dispatcher_type f1 = get_dispatcher(test1);
// Get a caller for the second function
dispatcher_type f2 = get_dispatcher(test2);
// Prepare arguments for the first function
vector<any> v = {string("hello"), 3.14};
// Invoke the first function
f1(v);
// Prepare arguments for the second function
v.assign({1, 42, string("hello"), string("world")});
// Invoke the second function
f2(v);
}
唯一的缺点是boost::any
你不能显式地分配字符串文字,因为字符串文字是 type char []
,并且数组不能用于初始化 type 的对象any
:
any a = "hello"; // ERROR!
因此,您必须将它们包装成string
对象,或者将它们显式转换为指向的指针char const*
:
any a = string("hello"); // OK
any b = (char const*)"hello"; // OK
如果这对您来说不是一个大问题,那么最好选择第二种解决方案。