问题
From std::add_pointer
Possible implementation
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)) {};
The description for the above (possible) implementation reads:
If T is a reference type, then provides the member typedef type which is a pointer to the referred type.
Otherwise, if T names an object type, a function type that is not cv- or ref-qualified, or a (possibly cv-qualified) void type, provides the member typedef type which is the type T*.
Otherwise (if T is a cv- or ref-qualified function type), provides the member typedef type which is the type T.
In the (possible) implementation code above, apparently struct add_pointer
derives from the type returned by detail::try_add_pointer<T>(0)
.
What is the logic behind deriving from the type returned by the overload of detail::try_add_pointer<T>
taking int
argument, to resolve the member typedef type
to one of the three possibilities described above? Specifically, how does this address the possibility if T
is a cv-
or ref-
qualified function type?
回答1:
The key is in understanding how overload resolution for detail::try_add_pointer<T>(0)
works. Substituting T
into detail::try_add_pointer
is meant to produce an overload set that will always contain at least one member (the variable argument overload).
Whether or not the overload taking an int
is discarded during overload resolution (SFINAE) is determined by the success of substituting T
into typename std::remove_reference<T>::type*
. When substitution succeeds, the overload exists, and is a better match in overload resolution for 0 (ellipsis are the worst possible match compared an any other conversion sequence). Either way, whichever overload is picked up in overload resolution, decltype(detail::try_add_pointer<T>(0))
will resolve to something that has a nested ::type
member.
So let's go on a case by case analysis:
"If T is a reference type" - Let's mark it
T = T2&
. Thenstd::remove_reference<T>::type
isT2
. The types to which we may form a reference are also the types to which we may form a pointer. Sostd::remove_reference<T>::type*
is well-formed (it'sT2*
), and the first overload exists. It's picked up in overload resolution. The nested::type
of its return type isT2*
."Otherwise, if T names an object type, a function type that is not cv- or ref-qualified, or a (possibly cv-qualified) void type" - In this case
std::remove_reference<T>::type
is simplyT
. We can form a pointer to any of the types in the previous list, and sostd::remove_reference<T>::type*
is well formed again (and it'sT*
). The first overload again exists and is picked up in overload resolution. The nested::type
of its return type isT*
."Otherwise (if T is a cv- or ref-qualified function type)" - The interesting bit. This speaks of types like
void (int) const&
. Here againstd::remove_reference<T>::type
isT
. But we are not allowed to form a pointer toT
, the base language prohibits it. Thereforestd::remove_reference<T>::type*
is ill-formed, and for this overload resolution the first overload is disregarded. Left with only the second overload, it's the one picked up by overload resolution. The nested::type
of its return type isT
.
回答2:
The inheritance is just a way of using the type_identity
wrapper (already needed to form valid return types) to define the trait with minimal effort. The overload trick relies on the fact that std::remove_reference
is the identity for non-reference types and that (having dealt with references) std::add_pointer<T>
is T*
except when there is no such type at all. In that case, SFINAE kills off the int
overload and the ...
is selected (where just T
is produced).
来源:https://stackoverflow.com/questions/57506069/a-question-regarding-the-implementation-of-stdadd-pointer