给出此代码:
#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
解决您的问题:
#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)>() );
}
发布的代码有两个问题。首先,之间的歧义:
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 ++标准的人们。