我正在努力思考可变参数模板,并认为一个应该采用任意参数(不同类型)的简单函数将是一个很好的练习。
第一次尝试
template<typename T>
typename std::enable_if<std::is_integral<T>::value, std::string>::type
concater ( T x ) { return std::to_string(x); }
template<typename T>
typename std::enable_if<std::is_floating_point<T>::value, std::string>::type
concater ( T x ) { return std::to_string(x); }
template<typename T>
typename std::enable_if<std::is_convertible<T,std::string>::value, std::string>::type
concater ( T x ) { return std::string(x); }
template<typename T, typename... Rest>
typename std::enable_if<std::is_integral<T>::value, std::string>::type
concater ( T x, Rest... xs ) { return std::to_string(x) + "; " + concater( xs... ); }
template<typename T, typename... Rest>
typename std::enable_if<std::is_floating_point<T>::value, std::string>::type
concater ( T x, Rest... xs ) { return std::to_string(x) + "; " + concater( xs... ); }
template<typename T, typename... Rest>
typename std::enable_if<std::is_convertible<T,std::string>::value, std::string>::type
concater ( T x, Rest... xs ) { return std::string(x) + "; " + concater( xs... ); }
template<typename... Values>
std::string to_csv ( Values... vs )
{
return concater( vs... );
}
它确实有效,但它并不好。它也可能不会推广到其他类型。所以我做了第二次尝试并想出使用 <<
运算符将使函数更通用。
第二次尝试
template<typename Stream, typename T>
void concat2 ( Stream & s, T x ) { s << x; }
template<typename Stream, typename T, typename... Rest>
void concat2 ( Stream & s, T x, Rest... xs )
{
s << x << ";";
concat2( s, xs... );
}
template<typename... Values>
std::string to_csv2 ( Values... vs )
{
std::ostringstream oss;
concat2( oss, vs... );
return oss.str();
}
在我看来,它更短而且看起来更漂亮。但似乎还是差了一点点。您将如何完成这项任务,或者有没有办法让它变得更好?
我也想知道这是不是:
std::cout << to_csv(1,"Hello",3) << std::endl;
会被编译成类似这样的东西:
std::cout << 1 << "Hello" << 3 << std::endl;
有什么办法可以实现吗?
最佳答案
再一次,braced-init-list 中的包扩展来拯救。
template<typename Value, typename... Values>
std::string to_csv2 ( Value v, Values... vs )
{
std::ostringstream oss;
using expander = int[];
oss << v; // first
(void) expander{ 0, (oss << ";" << vs, void(), 0)... };
return oss.str();
}
Demo .
思路是先打印第一个元素;然后在单个包扩展中打印剩余的元素,每个元素前面都有分隔符。我们扩展模式(oss << ";" << vs, void(), 0)
在临时数组的初始化列表中,因此给定 vs
作为包含 v1, v2, v3
的包装它扩展到
(void) expander{ 0, (oss << ";" << v1, void(), 0),
(oss << ";" << v2, void(), 0),
(oss << ";" << v3, void(), 0) };
括号内的逗号是逗号运算符,计算左操作数,丢弃结果,然后计算右操作数。 void()
防止可能存在的任何逗号运算符重载(因为我们不知道值的类型以及 oss << vs
可能返回的内容)。
每个表达式的计算结果都为 0,即逗号运算符最右边的操作数,用于初始化临时数组中我们实际上并不关心的元素。如果 vs
,则需要第一个 0 以确保我们不会创建非法的零大小数组。是一个空包。
最终结果是评估每个初始化器子句会将前面带有分隔符的相应变量输出到流中,并且标准中严格保证这些初始化器子句是从左到右评估的,所以我们将以正确的顺序将值发送到流。
关于c++ - C++ 中的可变参数模板和逗号分隔的字符串,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28547273/