模板递归调用与折叠表达式为何在类型转换时存在输出差异?
问题:C++递归打印函数与折叠表达式打印函数输出不一致的原因
原代码
#include <array> #include <type_traits> #include <iostream> template<class... Types> class Test { public: Test() : format_({ [] { if constexpr(std::is_same_v<Types, int64_t>) { return "%ld "; } else if constexpr(std::is_same_v<Types, int32_t>) { return "%d "; } else if constexpr(std::is_same_v<Types, double>) { return "%f "; } }() ... }) {} template<typename T, typename ... Args> void print_recursive(T && v, Args && ... args) { size_t i = sizeof...(Types) - sizeof...(args) - 1; printf(format_[i], std::forward<T>(v)); if constexpr(sizeof...(Args) > 0) { print_recursive(std::forward<Args>(args)...); } else { printf("\n"); } } void print_fold(Types && ... args) { size_t i = 0; ([&]{ printf(format_[i++], args); }(), ...); printf("\n"); } private: std::array<const char*, sizeof...(Types)> format_; }; int main() { Test<int64_t, int32_t, double> t; t.print_recursive(1.0, 2, 3.0); t.print_fold(1.0, 2, 3.0); }
输出结果
0 2 3.000000 1 2 3.000000
编译运行命令
g++ test.cpp -o test -std=c++17 && ./test
编译器信息
$ gcc --version gcc (GCC) 9.2.0 Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
问题描述
上述代码中,format_数组通过折叠表达式初始化,两个打印函数使用的格式说明符对应索引一致,传入的参数也相同,但输出结果却不一致,这是什么原因?
原因分析
1. print_fold的参数处理
print_fold的参数是Types&&... args,与类模板参数int64_t, int32_t, double严格匹配。调用t.print_fold(1.0, 2, 3.0)时:
- 第一个参数
1.0(double类型)会被隐式转换为int64_t,值为1; - 第二个参数
2(int类型)转换为int32_t,值为2; - 第三个参数
3.0保持double类型不变。
打印时每个参数都对应正确的格式说明符,因此输出正确的1 2 3.000000。
2. print_recursive的参数处理
print_recursive是模板函数,参数类型由推导而来,不会自动转换为类模板的Types类型:
- 第一次调用
t.print_recursive(1.0, 2, 3.0)时,T推导为double,Args推导为int, double。计算索引i = 3 - 2 - 1 = 0,对应format_[0]是为int64_t设计的%ld,但实际传入的是double类型的1.0,用%ld打印double属于未定义行为,当前编译器下输出0; - 第二次递归调用
print_recursive(2, 3.0),T推导为int,Args推导为double,索引i = 3 -1 -1 =1,对应format_[1]的%d,参数是int类型的2,输出正确; - 第三次递归调用
print_recursive(3.0),T推导为double,Args为空,索引i=3-0-1=2,对应format_[2]的%f,参数是3.0,输出正确。
最终得到错误的0 2 3.000000。
修正方案
新增入口函数,先将参数转换为类模板的Types类型,再传入递归实现函数,确保参数类型与格式说明符匹配:
void print_recursive(Types && ... args) { print_recursive_impl(std::forward<Types>(args)...); } template<typename T, typename ... Args> void print_recursive_impl(T && v, Args && ... args) { size_t i = sizeof...(Types) - sizeof...(args) - 1; printf(format_[i], std::forward<T>(v)); if constexpr(sizeof...(Args) > 0) { print_recursive_impl(std::forward<Args>(args)...); } else { printf("\n"); } }
该方案中,入口函数强制将传入参数转换为Types对应类型,递归时参数类型与格式说明符完全匹配,即可得到正确输出。
内容的提问来源于stack exchange,提问作者ajoseps
相关产品推荐
相关产品推荐

