我正在尝试下面的示例来了解可变参数模板,并发现其行为有些不一致。
#include <iostream>
#include <string>
using namespace std;
template<typename T>
T adder(T v) {
return v;
}
template<typename T, typename... Args>
T adder(T first, Args... args) {
return first + adder(args...);
}
int main()
{
long sum = adder(1, 2, 3, 8, 7); //Works
cout << sum << endl;
string s1 = "xx", s2 = "aa", s3 = "bb", s4 = "yy";
string ssum = adder(s1, s2, s3, s4); //Works
cout << ssum << endl;
string ssum2 = s1 + s2 + "3" + "4"; //Works as operator '+' is defined in string class for const char*
cout << ssum2 << endl;
string ssum3 = adder(s1, s2, "3", "4"); //Does not work. Expected as binary 'operator+' is not defined for string and const char*
cout << ssum3 << endl;
string ssum4 = adder("3", "4"); //Does not work. Expected as binary 'operator+' is not defined for const char* and const char*
cout << ssum4 << endl;
string ssum5 = adder(s1, s2, "3"); //Works!!!
cout << ssum5 << endl;
}
调用adder
和ssum3
的{{1}}失败,但适用于ssum4
。这种行为是否一致?是因为ssum5
的最后一个参数在最终迭代时转换为ssum5
吗?
答案 0 :(得分:4)
是因为
ssum5
的最后一个参数在最后一次迭代时转换为string
了吗?
否,之所以有效,是因为std::operator+(std::basic_string)
被重载以获取std::string
和原始字符串(const char*
)。假设s2
是std::string
,那么s2 + "3"
和"3" + s2
都可以正常工作。之所以ssum5
之所以有效是因为最后一次递归将被解释为s2 + "3"
,这很好。
问题是您不能将两个连续的原始字符串传递给adder
。对于ssum3
和ssum4
,您要传递"3"
和"4"
,最后它们将被解释为"3" + "4"
,显然不会起作用。 / p>
答案 1 :(得分:2)
您可以使用std::common_type
对其进行修复。
正如其他人所指出的,这与递归的顺序有关。
adder(s1, s2, "3")
与:
s1 + adder(s2, "3")
之所以有效,是因为它与(合法之和std::strings
,也合法之和std::string
与const char*
一样)
s1 + (s2 + ("3")))
另一方面
adder(s1, s2, "3", "4");
显然与它相同(最终添加两个const char*
,这是非法的),因此无法工作:
s1 + (s2 + ("3" + ("4")))
要解决此问题,您应该使用std::common_type
,它将以通用类型(在这种情况下为std::string
)执行所有加法操作:
template<typename T>
T adder(const T & v) {
return v;
}
template<typename T, typename... Args>
T adder(const T & first, const Args &... args) {
return first + adder<typename std::common_type<T, Args...>::type>(args...);
}
另一种选择是使用C ++ 17折叠表达式(如果您有C ++ 17):
template <typename ... Args>
typename std::common_type<Args...>::type
adder2(const Args & ... args)
{
using type = typename std::common_type<Args...>::type;
return (type(args) + ... );
}
缺点是它会导致字符串等类型的复制构造函数,并且需要C ++ 17。通过使用辅助函数,可以消除多余的构造(包括常见的基类):
template <typename Target, typename Source>
typename std::enable_if< ! std::is_base_of<Target, Source>::value,
Target>::type
toType(const Source & source)
{
return source;
}
template <typename Target, typename Source>
typename std::enable_if<std::is_base_of<Target, Source>::value,
const Target&>::type
toType(const Source & source)
{
return source;
}
template <typename ... Args>
typename std::common_type<Args...>::type
addder3(const Args & ... args)
{
using type = typename std::common_type<Args...>::type;
return (toType<type>(args) + ... );
}
答案 2 :(得分:1)
ssum3为adder(s1, adder(s2, adder("3", adder("4"))))
ssum5为adder(s1, adder(s2, adder("3")))
第一种情况包含实例adder("3", adder("4"))
,该实例最终扩展到"3" + "4"
,显然不起作用。
第二种情况永远不会尝试将两个const char*
加在一起,所以很好。