C++11 模板可变参数: 从容器中解包参数包的问题

www_adintr_com 2016-09-08 02:14:14
在一些脚本语言和 C/C++ 的交互中, 脚本语言调用 C/C++ 函数时往往会把参数打包到一个数组中, C++ 函数往往需要先把脚本参数解包为 C++ 类型的参数再完成实际的功能. 在 C++11 之前, 对这部分的封装往往是针对不同的参数个数实现一个版本. 在 C++11 之后, 用可变模板参数来实现成了自然地想法. 但是在使用中却出现了一些问题, 望熟悉 C++11 的大侠们指点.

下面是一个简化的模型:

#include <stdio.h>
#include <vector>

// 需要封装的功能
class GetVectorVal
{
public:
GetVectorVal(const std::vector<double>& vec)
: m_vec(vec), m_index(0)
{
}

template <typename T>
T get_val()
{
return (T)m_vec[m_index++]; // 实际系统中这里会有复杂的参数类型验证和装换操作
}

private:
const std::vector<double>& m_vec;
int m_index;
};

template <typename... ArgsType>
void vector_call(const std::vector<double>& args, int(*func)(ArgsType...))
{
GetVectorVal ArgGetter(args);
func(ArgGetter.get_val<ArgsType>()...); //!! 这里 get_val 改变了状态, 依赖于编译器的参数求值顺序
}

// 各式各样的 C++ 函数对象, 测试用
int print1(int x)
{
printf("%d\n", x);
return 1;
}

int printfloat(float x)
{
printf("%.4f\n", x);
return 1;
}

int print3(int x, int y, int z)
{
printf("%d %d %d\n", x, y, z);
return 1;
}

// 测试
int main()
{
std::vector<double> args = { 10.341, 20, 30, 40, 50 };

vector_call(args, print1);
vector_call(args, printfloat);
vector_call(args, print3);
}


在从数组获取参数的时候, 需要有一个状态, 当前是第几个参数, 然后对应找数组的第几个元素. 但是 C++ 里并没有类似 sizeof... 这样的 indexof... 操纵符. 于是在外面保存了一个状态, 获取完参数后改变这个状态. 于是就出现了上面注释中说的会依赖编译器的参数求值顺序的问题.

1. C++ 里面有没有方法可以得到当前解包参数位置? 然后可以这样
func(ArgGetter.get_val<ArgsType>(indexOf...ArgsType)...);
来传入位置, 不用外面保存.

2. 如何在函数参数外面使用可变参数包?
我这样试的时候会得到语法错误:

ArgsType... realargs = ArgGetter.get_val<ArgsType>()...;
func(realargs...);


3. 有没有其他方法来解决这个问题?
...全文
605 点赞 收藏 10
写回复
10 条回复
切换为时间正序
当前发帖距今超过3年,不再开放新的回复
发表回复
studyu 2019-08-10
如果使用 void* 转换不同类型参数时,会有错误,指向引用的指针,因为调用的函数会有参数时引用的
回复
www_adintr_com 2016-09-08
引用 8 楼 akirya 的回复:
[quote=引用 6 楼 adlay 的回复:] 用 std::index_sequence 确实可以了, std::tuple 似乎用不上

template <typename... ArgsType, size_t... Index>
void vector_call_imp(const std::vector<double>& args, int(*func)(ArgsType...), std::index_sequence<Index...>)
{
	GetVectorVal gv(args);
	func(gv.get_val<ArgsType>(Index)...);
}

template <typename... ArgsType>
void vector_call(const std::vector<double>& args, int(*func)(ArgsType...))
{
	vector_call_imp(args, func, std::make_index_sequence<sizeof...(ArgsType)>());
}
因为你的参数都是同类型的,所以放数组就可以。 要是参数是不同类型的话就得用tuple。[/quote] 实际上函数的参数都是不同类型的, vector 里面是一个类似 void* 的东西, 根据函数的实际参数类型来把 void* 转换成不同的类型. 这里简化了, 只用了个 float 来测试不同的类型, 但类型的转换这个流程还是在的.
回复
引用 6 楼 adlay 的回复:
用 std::index_sequence 确实可以了, std::tuple 似乎用不上

template <typename... ArgsType, size_t... Index>
void vector_call_imp(const std::vector<double>& args, int(*func)(ArgsType...), std::index_sequence<Index...>)
{
	GetVectorVal gv(args);
	func(gv.get_val<ArgsType>(Index)...);
}

template <typename... ArgsType>
void vector_call(const std::vector<double>& args, int(*func)(ArgsType...))
{
	vector_call_imp(args, func, std::make_index_sequence<sizeof...(ArgsType)>());
}
因为你的参数都是同类型的,所以放数组就可以。 要是参数是不同类型的话就得用tuple。
回复
pengzhixi 2016-09-08
template<class T, size_t N>  void f( T (&array)[N])
{
	for (int i = 0; i < N; ++i)
		cout << array[i] << endl;
}

int main()
{
	double a[] = { 10.341, 20, 30, 40, 50 };
	f(a);
	return 0;
}
虽然没看懂 但是不知道这个代码对你是否有帮助
回复
www_adintr_com 2016-09-08
用 std::index_sequence 确实可以了, std::tuple 似乎用不上

template <typename... ArgsType, size_t... Index>
void vector_call_imp(const std::vector<double>& args, int(*func)(ArgsType...), std::index_sequence<Index...>)
{
	GetVectorVal gv(args);
	func(gv.get_val<ArgsType>(Index)...);
}

template <typename... ArgsType>
void vector_call(const std::vector<double>& args, int(*func)(ArgsType...))
{
	vector_call_imp(args, func, std::make_index_sequence<sizeof...(ArgsType)>());
}
回复
引用 3 楼 adlay 的回复:
能具体一点吗, 这样编译不过:

	std::tuple<ArgsType...> t;
	auto l = std::make_index_sequence<sizeof...(ArgsType)>();
	func(std::get<l>(t)...); // error C3546: '...': there are no parameter packs available to expand
需要一个函数做中转 这个是我随便写的
#include <stdio.h>
#include <tuple>
void func( int a, double b, const char* c )
{
    printf( "%d\t%f\t%s\n", a, b, c );
}

template<class F, class T , size_t... I>
void invoke_impl( F f, T t, std::index_sequence<I...> )
{
    f( std::get<I>( t )...  );
}


template<class F, class... T >
void invoke( F f, T... t )
{
    invoke_impl( f,  std::make_tuple( t... ) , std::make_index_sequence<sizeof...( T )>() );
}

int main()
{
    invoke( func , 1, 2.0, "ccc" );
}
标准库也提供了std::invoke
回复
www_adintr_com 2016-09-08
引用 2 楼 akirya 的回复:
测试例子     std::vector<double> args = { 10.341, 20, 30, 40, 50 };     vector_call(args, print1);     vector_call(args, printfloat);     vector_call(args, print3); 没怎么看懂要输出什么样的结果.
输出什么无所谓, 效果是 把 args[0] 作为 后面函数的 第一个参数 把 args[1] 作为 后面函数的 第二个参数 把 args[2] 作为 后面函数的 第三个参数 ...
回复
www_adintr_com 2016-09-08
引用 1 楼 akirya 的回复:
std::bind有现成的解决方案。 将参数打包到 std::tuple 用 std::make_index_sequence得到一个 std::index_sequence<I...> tuple t ; func( std::get<I>(t) ... ); 这么调用就可以了
能具体一点吗, 这样编译不过:

	std::tuple<ArgsType...> t;
	auto l = std::make_index_sequence<sizeof...(ArgsType)>();
	func(std::get<l>(t)...); // error C3546: '...': there are no parameter packs available to expand
回复
测试例子     std::vector<double> args = { 10.341, 20, 30, 40, 50 };     vector_call(args, print1);     vector_call(args, printfloat);     vector_call(args, print3); 没怎么看懂要输出什么样的结果.
回复
std::bind有现成的解决方案。 将参数打包到 std::tuple 用 std::make_index_sequence得到一个 std::index_sequence<I...> tuple t ; func( std::get<I>(t) ... ); 这么调用就可以了
回复
相关推荐
发帖
C++ 语言
创建于2007-09-28

6.0w+

社区成员

C++ 语言相关问题讨论,技术干货分享,前沿动态等
申请成为版主
帖子事件
创建了帖子
2016-09-08 02:14
社区公告
暂无公告