可能的实现
namespace detail {
template <class T>
struct type_identity { using type = T; }; // or use std::type_identity (since C++20)
template <class T>
auto try_add_pointer(int) -> type_identity<typename std::remove_reference<T>::type*>;
template <class T>
auto try_add_pointer(...) -> type_identity<T>;
} // namespace detail
template <class T>
struct add_pointer : decltype(detail::try_add_pointer<T>(0)) {};
上述(可能)实现的说明为:
如果T是引用类型,则提供成员typedef类型,其中 是指向所引用类型的指针。
否则,如果T命名为对象类型,则该函数类型不是cv- 或ref限定,或(可能是cv限定)void类型,可提供 成员typedef类型,即类型T *。
否则(如果T是cv或ref限定的函数类型),则提供 成员typedef类型,即类型T。
在上面的(可能的)实现代码中,显然struct add_pointer
源自detail::try_add_pointer<T>(0)
返回的类型。
从detail::try_add_pointer<T>
的重载返回int
到将成员typedef type
解析为上述三种可能性之一的背后得出的逻辑是什么?具体来说,如果T
是cv-
或ref-
限定的函数类型,这如何解决可能性?
答案 0 :(得分:2)
关键在于了解detail::try_add_pointer<T>(0)
的重载解析的工作方式。将T
替换为detail::try_add_pointer
是为了产生一个重载集,该重载集将始终包含至少一个成员(变量参数重载)。
在过载解析(SFINAE)期间是否丢弃承受int
的过载取决于是否将T
替换为typename std::remove_reference<T>::type*
。替换成功后,将存在过载,并且过载分辨率与0匹配更好(与其他任何转换序列相比,省略号是最糟糕的匹配)。不管采用哪种方式,无论以重载分辨率获得重载,decltype(detail::try_add_pointer<T>(0))
都将解析为具有嵌套::type
成员的对象。
所以让我们逐个案例进行分析:
“如果T是引用类型”-我们将其标记为T = T2&
。那么std::remove_reference<T>::type
是T2
。我们可以形成引用的类型也是我们可以形成指针的类型。因此std::remove_reference<T>::type*
的格式正确(它是T2*
),并且存在第一个重载。它是在过载解析中获得的。其返回类型的嵌套::type
是T2*
。
“否则,如果T命名为对象类型,非cv或ref限定的函数类型或(可能是cv限定的)无效类型”,在这种情况下,std::remove_reference<T>::type
为只需T
。我们可以形成一个指向上一个列表中任何类型的指针,因此std::remove_reference<T>::type*
的格式再次正确(它是T*
)。第一个重载再次存在,并以重载分辨率进行拾取。其返回类型的嵌套::type
是T*
。
“否则(如果T是cv或ref限定的函数类型)” –有趣的位。这涉及到void (int) const&
之类的类型。这里std::remove_reference<T>::type
再次是T
。但是我们不允许形成指向T
的指针,基本语言禁止使用它。因此std::remove_reference<T>::type*
格式错误,对于此重载解决方案,将忽略第一个重载。只剩下第二个过载,它是由过载分辨率拾取的。其返回类型的嵌套::type
是T
。
答案 1 :(得分:1)
继承只是使用type_identity
包装器(已经需要形成有效的返回类型)来定义特征的一种方法。重载技巧依赖于以下事实:std::remove_reference
是非引用类型的标识,std::add_pointer<T>
是T*
(已处理引用),除非没有此类类型。在这种情况下,SFINAE消除了int
的重载,并选择了...
(仅生成T
)。