以下Variadic模板行为是否不一致?

时间:2018-08-14 07:59:19

标签: c++ string templates variadic-templates

我正在尝试下面的示例来了解可变参数模板,并发现其行为有些不一致。

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

}   

调用adderssum3的{​​{1}}失败,但适用于ssum4。这种行为是否一致?是因为ssum5的最后一个参数在最终迭代时转换为ssum5吗?

3 个答案:

答案 0 :(得分:4)

  

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

否,之所以有效,是因为std::operator+(std::basic_string)被重载以获取std::string和原始字符串(const char*)。假设s2std::string,那么s2 + "3""3" + s2都可以正常工作。之所以ssum5之所以有效是因为最后一次递归将被解释为s2 + "3",这很好。

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

答案 1 :(得分:2)

您可以使用std::common_type对其进行修复。

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

adder(s1, s2, "3")

与:

s1 + adder(s2, "3")

之所以有效,是因为它与(合法之和std::strings,也合法之和std::stringconst 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*加在一起,所以很好。