我有以下代码:
#include <iostream>
#include <type_traits>
template <typename T> struct CSizex {
};
struct CSize : public CSizex<int>
{
};
struct Logger
{
template <typename T>
Logger& operator<< (T& value)
{
return *this << const_cast<const T & >(value);
}
template <typename T> Logger& operator<<(const CSizex<T>& size)
{
std::cout << __FUNCSIG__;
return *this;
}
template <typename T>
Logger& operator<< (const T& value)
{
static_assert(std::is_arithmetic<T>::value || std::is_integral<T>::value || std::is_enum<T>::value, "This method is only meant for arithmetic types");
std::cout << __FUNCSIG__;
return *this;
}
};
int main()
{
CSize size;
Logger() << CSize();
return 0;
}
当我这样做时:
Logger() << CSize();
编译器正在尝试实例化Logger& operator<<(const T& value)
重载,当然,这会导致static_assert
失败。为什么Logger& operator<<(const CSizex<T>& size)
被认为不是更好的匹配?我该如何实现我想要的?
答案 0 :(得分:2)
事实上,const CSizex<T>&
是完全匹配,因为它是身份转换; [over.ics.ref] / 1:
当引用类型的参数直接绑定到参数时 表达式,隐式转换序列是标识 转换,除非参数表达式的类型是a 派生类的参数类型,在这种情况下是隐式的 转换序列是派生到基础的转换(13.3.3.1)。
从导致错误的模板实例化的特化也是完全匹配: [over.ics.user] / 4:
将类类型的表达式转换为相同的类类型 给出精确匹配等级,以及类表达式的转换 类型到该类型的基类被赋予转换排名,尽管如此 复制/移动构造函数(即用户定义的)的事实 为这些情况调用转换函数。
但是,第一个模板比第二个模板更专业。从原始模板和转换后的模板的参数和参数类型中删除引用和cv限定符后,对于唯一类型Unique
,可以根据原始模板的参数推导出已转换模板的参数。其他模板:Unique
是针对CSizex<T>
推断的,这会导致扣减失败(因为Unique
不是CSizex
的特化),而CSizex<Unique>
是针对{T
推断的1}}会成功(T
本身就是CSizex<Unique>
)。所以第一个模板更专业,因此应该通过部分排序来选择
Clang compiles this correctly. GCC 4.9.0也是如此。也许你将问题简化为不再反映错误的代码。
<小时/>
现在,我们考虑
template <typename T>
Logger& operator<<(const CSizex<T>& size); // #1
template <typename T>
Logger& operator<< (const T& value); // #2
对于#2,参数推导为CSize
,因此参数为CSize const&
,而对于#1,参数的参数为CSize<int> const&
。
过载分辨率在上面的引用中明确说明:
当引用类型的参数直接绑定到参数时 表达式,隐式转换序列是标识 转换,除非参数表达式的类型是a 派生类的参数类型,在这种情况下是隐式的 转换序列是派生到基础的转换(13.3.3.1)。
...转换是#2的身份转换,但#1的派生到基本转换。由于排名较高,不难看出#2被选中,[over.best.ics] / 6:
派生到基础的转换具有转换等级(13.3.3.1.1)。
...身份转换具有完全匹配等级。
基本上,将静态断言的条件移动到enable_if
:
struct Logger
{
template <typename T>
Logger& operator<<(const CSizex<T>& size)
{
std::cout << __PRETTY_FUNCTION__ << '\n';
return *this;
}
template <typename T>
typename std::enable_if<std::is_arithmetic<T>::value
|| std::is_integral<T>::value
|| std::is_enum<T>::value, Logger&>::type
operator<<(const T& value)
{
std::cout << __PRETTY_FUNCTION__ << '\n';
return *this;
}
};