我试图使用DanielKO在这个问题中的答案来满足我的需求,但我不熟悉模板和可变函数,我不知道该怎么办。
我需要的是一个可变的c++(11)函数,我可以这样调用它:
String NewMsg = CreateMessage("SET",16,1,17,0,"RED",47);
并具有NewMsg="SET,0010000100110000,RED,002F"。
我甚至不知道该在论点之间加逗号。然后:在解析参数时,我如何区分整数和字符串,以便将每个整数格式化为十六进制字符串?
使用递归和函数重载
std::string CreateMessage(int i)
{
return /* i formatted as hex */;
}
std::string CreateMessage(const char* s)
{
return s;
}
template<typename T, typename... Ts>
std::string CreateMessage(T t, Ts... ts)
{
return CreateMessage(t) + "," + CreateMessage(ts...);
}
一种选择是对模板使用递归,就像Passer By在回答中所做的那样。然而,在我看来,一个更优雅的解决方案(如果您能够使用C++17 lanugage特性)是使用fold表达式来避免递归。表达式被扩展为直接为每个参数调用Append
,有点像编译时计算的参数之间的for循环。
template <class T>
void Append(std::ostringstream &out, T &&arg) {
out << "," << std::forward<T>(arg);
}
template <class... TArgs>
std::string CreateMessage(TArgs &&...args) {
std::ostringstream out;
(Append(out, std::forward<TArgs>(args)), ...);
return out.str().substr(1);
}
现场演示。
混合其他两种解决方案(Passer By的递归解决方案和Nick Mertin的C++17倍表达式解决方案),您也可以在C++11 中编写无递归的CreateMessage()
(以类似的倍表达式方式)
std::string const & getStr (std::string const & ret)
{ return ret; }
std::string getStr (int val)
{
std::ostringstream ret;
ret << std::hex << std::setw(4) << std::setfill('0') << val;
return ret.str();
}
template <typename ... Ts>
std::string CreateMessage (Ts const & ... args)
{
using unused = int[];
std::string ret = "";
std::string comma = "";
(void)unused { 0, ( ret += comma + getStr(args), comma = ",", 0 )... };
return ret;
}
--编辑--
OP询问
你愿意教我"相似折叠"线是如何工作的吗?我应该如何"阅读"它?
嗯。。。这条线是下面的
(void)unused { 0, ( ret += comma + getStr(args), comma = ",", 0 )... };
其中CCD_ 3是CCD_ 5的别名(using
)。
它使用了可变包(模板或函数参数)可以在C样式数组初始化的上下文中展开的事实,还使用了逗号运算符的威力(exec/compute的逗号运算符的适当性,并丢弃逗号左侧的内容)。
所以你有的展开式(对于args...
中的每个自变量)
( ret += comma + getStr(args), comma = ",", 0 )
其中
1) 将comma + getStr(args)
添加到ret
中,其中comma
对于第一个参数为空,并且对于以下参数等于","
(请参见(2)
2) 第一个逗号运算符丢弃ret
的值,并将","
分配给comma
(因此在第一个ret +=
和","
中有一个空的comma
用于后面的ret +=
3) 第二个逗号运算符丢弃逗号的值并返回0到CCD_ 18 的初始化
因此,ret
随着所有getStr(args)
被","
分隔而递增,并且未使用的数组被初始化为零。
观察另外几点:
a) 在数组(未命名的unused
)初始化列表中,您有一个起始的、与变量无关的零({ 0,
);这在args...
列表为空的情况下是必要的,因此该行变为(void)unsed { 0 };
,这是合法的,而不是(void)unused { };
(没有零),这是语法错误
b)unused
前面是CCD_;这不是严格必要的,但对于避免"对象已定义但未使用"类型的警告很有用。