使用可变参数模板时发生意外的隐式转换?

问题描述 投票:5回答:2

给出此代码:

#include <iostream>

template<typename... Args> class X;

template<typename T>
class X<T> {
 public:
    T value;
    X(T value_) : value(value_) {}
};

template<typename T, typename... Args>
class X<T, Args...> : public X<Args...> {
 public:
    T value;
    X(T value_, Args... args) : value(value_), X<Args...>(args...) {}
};

template<typename T>
std::ostream& operator <<(std::ostream& stream, const X<T>& value_) {
    stream << value_.value;
    return stream;
}

template<typename T, typename... Args>
std::ostream& operator <<(std::ostream& stream, const X<T, Args...>& value_) {
    stream << value_.value << " " << static_cast<X<Args...> const& >(value_);
    return stream;
}

class Person : public X<std::string, int>{
 public:
    Person(std::string name, int age) : X<std::string, int>(name, age) {}
};


int main()
{
   std::cout << Person("Me", 35) << std::endl;
   return 0;
}

使用'g ++ -std = c ++ 11 main.cpp与gcc 4.7.2编译,在执行时给出以下输出:

35

我期望它能打印:

Me 35

因为它应该首先匹配运算符<行为的int,而没有打印出作为直接超类X 的行为的两个项目。这是预期的吗?

c++ c++11 casting variadic-templates implicit-conversion
2个回答
1
投票

解决您的问题:

#include <iostream>

template<typename... Args> class X;

template<typename T>
class X<T> {
 public:
    T value;
    explicit X(T value_) : value(value_) {}
};

template<typename T, typename... Args>
class X<T, Args...> : public X<Args...> {
 public:
    T value;
    explicit X(T value_, Args... args) : X<Args...>(args...), value(value_) {}
};

这种类型让我们使用标签分派来根据X的参数数量选择重载:

template<std::size_t> struct compile_time_size {};

这里有3个重载。 0个参数上的第一个错误,第二个处理1,第三个处理N

template<typename... Ts>
std::ostream& output_helper(std::ostream&, const X<Ts...>&, compile_time_size<0>) = delete;

template<typename T, typename... Ts>
std::ostream& output_helper(std::ostream& stream, const X<T, Ts...>& value, compile_time_size<1>)
{
    stream << value.value;
    return stream;
}
template<typename T, typename... Ts, std::size_t N>
std::ostream& output_helper(std::ostream& stream, const X<T, Ts...>& value, compile_time_size<N>)
{
    stream << value.value << " " << static_cast< X<Ts...> const& >(value);
    return stream;
}

我们的<<现在只是一个重载,这使得对此进行推理变得容易。然后,它分派给上述辅助函数,并显式传递长度。他们使用ADL重新分发回<<,以查找在自身之后声明的函数:

template<typename... Ts>
std::ostream& operator<<(std::ostream& stream, const X<Ts...>& value) {
    return output_helper( stream, value, compile_time_size<sizeof...(Ts)>() );
}

live example


1
投票

发布的代码有两个问题。首先,之间的歧义:

template<typename T>
std::ostream& operator <<(std::ostream& stream, const X<T>& value_) {

和:

template<typename T, typename... Args>
std::ostream& operator <<(std::ostream& stream, const X<T, Args...>& value_) {

对于空参数包。改为使用这两个功能,可以解决此问题:

template<typename T>
std::ostream& operator <<(std::ostream& stream, const X<T>& value_) {

和:

template<typename T, typename T2, typename... Args>
std::ostream& operator <<(std::ostream& stream, const X<T, T2, Args...>& value_) {

现在解决了模板匹配的歧义。

这使我们面临第二个问题。这行:

  std::cout << Person("Me", 35) << std::endl;

人员没有提供operator <X< std::string, int >和X< int >都具有这种实现。但是,编译器现在可以在这两者之间进行选择,因为它们都是Person的超类。

这可以通过两种方式解决,首先是添加特定的运算符<

std::ostream& operator <<(std::ostream& stream, const Person &value_) {
   stream << static_cast<const X<std::string, int, int>&>(value_);
   return stream;
}

效果很好,但我发现它过于冗长。或者,我们可以通过删除类层次结构并仅使用成员变量包含“其他”值来消除歧义:

template<typename T>
class X<T> {
public:
    T value;
    X(T value_) : value(value_) {}
};

template<typename T>
std::ostream& operator <<(std::ostream& stream, const X<T>& value_) {
   stream << value_.value << ".";
   return stream;
}


// Empty arg packs will match ambiguously with nothing so make sure the empty pack case is distanct from the base case

template<typename T, typename T2, typename... Args>
class X<T, T2, Args...> {
public:
   T value;
   X<T2, Args...> superValue;
   X(T value_, T2 arg, Args... args) : value(value_), superValue(arg, args...) {}
};

template<typename T, typename T2, typename... Args>
std::ostream& operator <<(std::ostream& stream, const X<T, T2, Args...>& value_) {
   stream << value_.value << "," << value_.superValue;
   return stream;
}

现在,只能将Person隐式转换为X < std::string,int >,并且别无其他,也没有歧义。最近的g ++和clang都可以毫无问题地进行编译。

关于是否在模板解析度上没有什么应该匹配空的arg包,或者在转换解析度方面是否应该匹配更直接的基类,而在转换分辨率方面更不推荐直接的基类,我留给了比我更精通c ++标准的人们。

© www.soinside.com 2019 - 2024. All rights reserved.