以下可变参数模板行为是否不一致?

Is the following Variadic template behavior inconsistent?

本文关键字:是否 不一致 变参 参数      更新时间:2023-10-16

我正在尝试以下示例来理解可变参数模板,发现行为有些不一致。

#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;  
}   

ssum3ssum4调用adder失败,但适用于ssum5。这种行为是否一致?发生这种情况是因为ssum5的最后一个参数在最后一次迭代中转换为string吗?

这是因为ssum5的最后一个参数在最终迭代时转换为string吗?

不,它之所以有效std::operator+(std::basic_string)是因为被重载以接受std::string和原始字符串 (const char*(。给定s2是一个std::string,那么s2 + "3""3" + s2都可以正常工作。所以ssum5工作,因为最后递归它会被解释为s2 + "3",这很好。

问题是您无法将两个连续的原始字符串传递给adder。对于ssum3ssum4你传递"3""4",最后它们将被解释为"3" + "4",这显然不起作用。

您可以使用std::common_type修复它。

正如其他人所指出的,这与递归的顺序有关。

adder(s1, s2, "3")

与以下相同:

s1 + adder(s2, "3")

这是有效的,因为这与(std::strings求和,并且还用const char*std::string求和,这是合法的(:

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) + ... );
}

ssum3 是adder(s1, adder(s2, adder("3", adder("4"))))

ssum5 是adder(s1, adder(s2, adder("3")))

第一种情况包含即时adder("3", adder("4")),它最终扩展到"3" + "4",这显然不起作用。

第二种情况永远不会尝试将两个const char*加在一起,所以没关系。