如何使用可变参数模板参数保存可变数量的参数?
Posted
技术标签:
【中文标题】如何使用可变参数模板参数保存可变数量的参数?【英文标题】:How to save variable number of arguments using variadic template arguments? 【发布时间】:2013-08-01 13:52:34 【问题描述】:我想创建一个模板类,它可以为这个函数存储函数指针和参数,以便以后可以使用这个参数调用该函数。
我想通用地写这个,而不是依赖于参数类型或数量。
这是使用 c++11 的可变参数模板的想法:
template<class T, typename... Params>
class LazyEvaluation
private:
// Function to be invoked later
T (*f)(Params...);
// Params for function f
Params... storedParams; // This line is not compilable!
bool evaluated;
T result;
public:
// Constructor remembers function pointer and parameters
LazyEvaluation(T (*f)(Params...),Params... params)
: f(f),
storedParams(params) //this line also cannot be compiled
// Method which can be called later to evaluate stored function with stored arguments
operator T&()
// if not evaluated then evaluate
if (! evaluated)
result = f(storedParams...);
evaluated = true;
return result;
如果可能的话,我希望至少有这个类类型的公共接口是安全的。尽管至少以某种方式完成这项工作更为重要。
我设法以某种方式保存了可变数量的参数。但我无法将它们传递给函数 f。我会把它写成答案,但我希望你在看到我丑陋的不工作尝试之前考虑自己的解决方案。
我正在尝试使用 Microsoft Visual C++ Compiler Nov 2012 CTP (v120_CTP_Nov2012) 编译上面的代码,但最好有独立于编译器的解决方案。
谢谢
【问题讨论】:
我认为这是重复的:***.com/questions/14833129/… 将它们存储在std::tuple<Params...>
...
@KennyTM 这应该是一个答案。
@KennyTM 我可以将std::tuple<Params...>
传递给存储的函数f
吗?
【参考方案1】:
这是我尝试解决的方法:
参数包可以递归扩展并保存每个参数。功能商店应该这样做。它使用一个(两次重载)辅助函数。
template<typename T>
void storeHelperFunction(void*& memory, T last)
*((T*)memory) = last;
memory = (void*)((char*)memory + sizeof(T));
template<typename T, typename... Params>
void storeHelperFunction(void*& memory, T first, Params... rest)
storeHelperFunction(memory, first);
storeHelperFunction(memory, rest...);
template<typename... Params>
void store(void* memory, Params... args)
// Copy of pointer to memory was done when passing it to this function
storeHelperFunction(memory, args...);
函数存储需要一个指向内存的指针,其中应该保存可变数量的参数。
指针可以指向一些动态分配的内存,或者更好的指向大小等于sizeof...(Params)
的结构。
这种具有任何所需大小的结构都可以使用模板元编程来构建:
template <int N>
struct allocatorStruct
char byte1;
allocatorStruct<N-1> next;
;
template <>
struct allocatorStruct<1> ;
我不知道标准是怎么说的,也不知道微软以外的其他编译器是如何编译它的。但是使用我的编译器,对于任何大于或等于 1 的 N,sizeof(allocatorStruct) 都等于 N。
因此allocatorStruct<sizeof...(Params)>
的大小与Params 相同。
创建与 Params 大小相同的东西的另一种方法是使用类型 char [sizeof...(Params)]
。这样做的缺点是,当您尝试将此类数组作为参数传递时,编译器仅传递指向此数组的指针。
这就是为什么最好使用allocatorStruct<sizeof...(Params)>
。
现在主要思想:
保存函数时,我们可以将其转换为:T (*)(allocatorStruct<sizeof...(Params)>)
。
保存函数的参数时,我们可以将它们保存为 allocatorStruct<sizeof...(Params)>
类型的结构体。
参数的大小是相同的。虽然函数指针与函数的类型有关,但函数指向的函数将正确获取其数据。
至少我希望如此。根据调用约定,我预计传递的参数可以重新排序或错误,因为从左到右保存参数和从右到左传递之间的差异。但事实并非如此。使用 __cdecl 调用约定,只有第一个参数被传递,另一个丢失了。使用其他调用约定,程序停止工作。
我没有花太多时间调试它并查看内存中的数据(在堆栈上)。这至少是正确的方法吗?
【讨论】:
内存对齐有问题。你不可能知道 'memory + sizeof(T)' 对参数列表中的下一个元素是一个很好的对齐方式,【参考方案2】:只需使用 lambda 表达式
// Some function.
int add(int a, int b)
return a + b;
auto lazyFunc = [] return add(1, 2); ;
std::cout << lazyFunc() << std::endl; // Evaluate function and output result.
如果你真的想创建一个只计算一次函数的类(懒惰地),使用可变参数模板,你可以在下面的代码中执行类似的操作。
我还制作了这样的类,这样您就不必在每次参数更改时都创建一个新实例。我使用std::tuple
来存储给定的参数,并与之前给定的参数进行比较。如果参数不同,则将重新评估该函数。
函数使用std::function
包装器传递和存储,因此我不必使用原始函数指针(糟糕)。
#include <iostream>
#include <functional>
#include <utility>
#include <tuple>
template <typename T>
class LazyEvaluation ;
template <typename ReturnType, typename... Params>
class LazyEvaluation<ReturnType(Params...)>
private:
std::function<ReturnType(Params...)> func_;
ReturnType result;
std::tuple<Params...> oldParams; // Contains the previous arguments.
public:
explicit LazyEvaluation(std::function<ReturnType(Params...)> func)
: func_(std::move(func))
template <typename... Args>
ReturnType operator() (Args&&... args)
auto newParams = std::make_tuple(std::forward<Args>(args)...);
// Check if new arguments.
if (newParams != oldParams)
result = func_(std::forward<Args>(args)...);
oldParams = newParams;
std::cout << "Function evaluated" << std::endl;
std::cout << "Returned result" << std::endl;
return result;
;
int main()
auto f = [] (int a, int b)
return a + b;
;
// Specify function type as template parameter.
// E.g. ReturnType(Param1Type, Param2Type, ..., ParamNType)
LazyEvaluation<int(int, int)> ld(f);
std::cout << ld(1, 2) << std::endl;
std::cout << ld(1, 2) << std::endl;
std::cout << ld(3, 4) << std::endl;
输出:
Function evaluated
Returned result
3
Returned result
3
Function evaluated
Returned result
7
【讨论】:
【参考方案3】:鉴于形成可变参数索引包的标准机械:
template <std::size_t... I> struct index_sequence ;
template <std::size_t N, std::size_t... I>
struct make_index_sequence : public make_index_sequence<N-1, N-1, I...> ;
template <std::size_t... I>
struct make_index_sequence<0, I...> : public index_sequence<I...> ;
并使用未打包的元组参数调用函数:
template <typename Function, typename... Types, std::size_t... I>
auto apply_(Function&& f, const std::tuple<Types...>& t, index_sequence<I...>)
-> decltype(std::forward<Function>(f)(std::get<I>(t)...))
return std::forward<Function>(f)(std::get<I>(t)...);
template <typename Function, typename... Types>
auto apply(Function&& f, const std::tuple<Types...>& t)
-> decltype(apply_(f, t, make_index_sequence<sizeof...(Types)>()))
return apply_(f, t, make_index_sequence<sizeof...(Types)>());
这很简单:
template<typename Function, typename... Params>
class LazyEvaluation
private:
typedef decltype(std::declval<Function>()(std::declval<Params>()...)) result_type;
// Function to be invoked later
Function f;
// Params for function f
std::tuple<Params...> storedParams;
mutable bool evaluated;
union
std::aligned_storage<sizeof(result_type)> space;
mutable result_type result;
;
// Method which can be called later to evaluate stored function with stored arguments
void evaluate() const
// if not evaluated then evaluate
if (! evaluated)
new (&result) result_typeapply(f, storedParams);
evaluated = true;
public:
// Constructor remembers function pointer and parameters
LazyEvaluation(Function f, Params... params)
: f(std::move(f)),
storedParams(std::move(params)...),
evaluated(false)
~LazyEvaluation()
if (evaluated)
result.~result_type();
operator result_type&()
evaluate();
return result;
operator const result_type& () const
evaluate();
return result;
;
template <typename Function, typename... Params>
LazyEvaluation<Function, Params...>
make_lazy(Function&& f, Params&&... params)
return std::forward<Function>(f), std::forward<Params>(params)...;
我使用联合和放置 new
来存储评估结果,这样它就不需要是默认可构造类型,还有一些 mutable
技巧以便可以转换 const LazyEvaluator
以及一个非常量实例。
【讨论】:
以上是关于如何使用可变参数模板参数保存可变数量的参数?的主要内容,如果未能解决你的问题,请参考以下文章