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?
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&. Then std::remove_reference<T>::type is T2. The types to which we may form a reference are also the types to which we may form a pointer. So std::remove_reference<T>::type* is well-formed (it's T2*), and the first overload exists. It's picked up in overload resolution. The nested ::type of its return type is T2*.
"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 simply T. We can form a pointer to any of the types in the previous list, and so std::remove_reference<T>::type* is well formed again (and it's T*). The first overload again exists and is picked up in overload resolution. The nested ::type of its return type is T*.
"Otherwise (if T is a cv- or ref-qualified function type)" - The interesting bit. This speaks of types like void (int) const&. Here again std::remove_reference<T>::type is T. But we are not allowed to form a pointer to T, the base language prohibits it. Therefore std::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 is T.
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).
Related
Consider a function template f that binds a non-const lvalue reference to a deduced non-type template parameter
template <auto N>
void f()
{
auto & n = N;
}
This works when f is instantiated over class types
struct S {};
f<S{}>(); // ok
But doesn't work when instantiated over non class types (as I expected)
f<42>(); // error non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int'
The same error is emitted if an lvalue argument is used to instantiate f as well
constexpr int i = 42;
f<i>(); // also error
Here's a demo to play with.
This looks like non-type template parameters of class type are lvalues, which seems odd. Where does the standard make a distinction between these kinds of instantiations, and why is there a difference if the argument to the template is of class type?
The reason for the distinction is because class non-type template parameters weren't always there. Originally, value template parameters could only be pointers, integers, or a few other things. Such parameters were simple and the value was just a single number known at compile-time. As such, making them rvalues (remember: prvalue is C++11) was OK.
Once NTTPs could be more complex object types, you have to start engaging with certain questions. Should you be able to do this:
template<std::array<int, 5> arr>
void func()
{
for(int i: arr)
//stuff
}
The obvious answer is "of course you should." But that would require that you can get a reference to arr itself. That's how range-based for is defined, after all.
Now that can still work. After all, range-based for uses auto&& to store its reference, so it can reference a prvalue. But that has consequences.
Namely, if you create a reference to a prvalue, that causes the materialization of a temporary. A new temporary object distinct from all other objects.
This means that if you use a class NTTP in multiple places, you will get different objects with different addresses and different addresses for their subobjects. And this is something you can detect, since you can get the addresses of their subobjects.
Forcing compile-time code to create temporaries every time you use the name is bad for performance. So two different uses of such a parameter need to result in talking about the same object.
Therefore, class NTTPs need to be lvalues; each use of the name within a template is referring to the same object. But you can't go back and make all existing NTTPs lvalues too; that would break existing code.
So that's where we are.
As for where this is defined, it is in [temp.param]/8:
An id-expression naming a non-type template-parameter of class type T denotes a static storage duration object of type const T, known as a template parameter object, whose value is that of the corresponding template argument after it has been converted to the type of the template-parameter. All such template parameters in the program of the same type with the same value denote the same template parameter object.
std::declval is a compile-time utility used to construct an expression for the purpose of determining its type. It is defined like this:
template< class T >
typename std::add_rvalue_reference<T>::type declval() noexcept;
Would this not be simpler instead?
template< class T >
T declval() noexcept;
What is the advantage of a reference return type? And shouldn't it be called declref?
The earliest historical example I find is n2958, which calls the function value() but already always returns a reference.
Note, the operand of decltype does not need to have an accessible destructor, i.e. it is not semantically checked as a full-expression.
template< typename t >
t declprval() noexcept;
class c { ~ c (); };
decltype ( declprval< c >() ) * p = nullptr; // OK
The "no temporary is introduced for function returning prvalue of object type in decltype" rule applies only if the function call itself is either the operand of decltype or the right operand of a comma operator that's the operand of decltype (§5.2.2 [expr.call]/p11), which means that given declprval in the OP,
template< typename t >
t declprval() noexcept;
class c { ~ c (); };
int f(c &&);
decltype(f(declprval<c>())) i; // error: inaccessible destructor
doesn't compile. More generally, returning T would prevent most non-trivial uses of declval with incomplete types, type with private destructors, and the like:
class D;
int f(D &&);
decltype(f(declprval<D>())) i2; // doesn't compile. D must be a complete type
and doing so has little benefit since xvalues are pretty much indistinguishable from prvalues except when you use decltype on them, and you don't usually use decltype directly on the return value of declval - you know the type already.
Arrays cannot be returned by value thus even just the declaration of a function returning an array by value is invalid code.
You can however return an array by reference.
The purpose of decltype() is to have an expression that acts as a valid value of type T, to put it as a T in expressions expecting Ts. The problem is that in C++ a type Tcan be non-copyable, or even non-default-constructible. So using the T{} for that purpose doesn't work.
What decltype() does is to return an rvalue-reference to a T. An rvalue reference should be valid for any type T, so its guaranteed that we have a valid T from an rvalue reference of T, and its guaranteed we can have an rvalue reference to any type T. Thats the trick.
Think about decltype() as "give me a valid expression of type T". Of course its usage is intended for overload resolution, type determination, etc; since its purpose is to return a valid expression (In the syntactical sense), not to return a value. Thats reflected in the fact that std::declval() is not defined at all, its only declared.
If it was defined, we have the initial problem again (We have to construct a value for an arbitrary type T, and thats not possible).
template<auto>
struct S {};
template<S>
struct T {};
using T0 = T<S<0>{}>; // compiles
template<S s>
using T1 = T<s>; // fails
T0 compiles with GCC 9.1 but T1 does not:
error: class template argument deduction failed
error: no matching function for call to 'S(S<...auto...>)'
Is this expected behavior in C++2a?
EDIT:
The compilation failure appears to occur whenever the type is deduced, not specifically when a placeholder for a deduced class type is used:
template<auto s>
using T1 = T<s>; // also fails
T0 should be ill-formed because S<0> does not have strong structural equality.
T1 should be ill-formed, no diagnostic required, because as it stands there is no specialization of T1 that would be valid (because there is no specialization of S that has strong structural equality).
This is because the requirement on class types as non-type template parameters is, from [temp.param]/4:
A non-type template-parameter shall have one of the following (optionally cv-qualified) types:
a literal type that has strong structural equality ([class.compare.default]),
an lvalue reference type,
a type that contains a placeholder type ([dcl.spec.auto]), or
a placeholder for a deduced class type ([dcl.type.class.deduct]).
template<S>
struct T {};
falls into the last case. But when we try T<S<0>{}>, and we deduce the placeholder S as S<0> from CTAD, we hit [temp.arg.nontype]/1:
If a deduced parameter type is not permitted for a template-parameter declaration ([temp.param]), the program is ill-formed.
Which takes us back to the original reference and now we require something called "strong structural equality." This is defined in [class.compare.default]/4 as:
A type C has strong structural equality if, given a glvalue x of type const C, either:
C is a non-class type and x <=> x is a valid expression of type std::strong_ordering or std::strong_equality, or
C is a class type where all of the following hold:
All of C's base class subobjects and non-static data members have strong structural equality.
C has no mutable or volatile non-static data members.
At the end of the definition of C, overload resolution performed for the expression x == x succeeds and finds either a friend or public member == operator that is defined as defaulted in the definition of C.
In our case, we're a class type, but while we all our subobjects have strong structural equality (trivially as we have no subobjects), we do not have the appropriate operator==. To fix that:
template <auto>
struct S {
friend bool operator==(S const&, S const&) = default;
};
And now everything should compile (but doesn't because gcc doesn't completely support this yet, and indeed doesn't even let you declare the required defaulted operator==).
A follow-up of this question. Assuming placeholder can be used to deduce result type of the function pointer constituting non-type template parameter. Does c++17 allows to perform overload resolution on passed to the template function name - without the knowledge of the result type, that would be needed to perform implicit casting?
template <auto(*)(int)>
struct Foo { };
int bar(int);
float bar(float);
int main() {
static_cast<void>(Foo<bar>{});
}
[gcc] as well as [clang] seem to accept the code.
Yes, it's allowed according to the very bullet Rakete1111 pointed out. And there's no need to just assume it can be done, it's done according to the rules of placeholder type deduction at [dcl.type.auto.deduct]/4, emphasis mine:
If the placeholder is the auto type-specifier, the deduced type T'
replacing T is determined using the rules for template argument
deduction. Obtain P from T by replacing the occurrences of auto with
either a new invented type template parameter U or, if the
initialization is copy-list-initialization, with
std::initializer_list. Deduce a value for U using the rules of
template argument deduction from a function call, where P is a
function template parameter type and the corresponding argument is e.
If the deduction fails, the declaration is ill-formed. Otherwise, T'
is obtained by substituting the deduced U into P.
Where [temp.deduct.call]/6 has this paragraph, pertaining to your use case:
When P is a function type, function pointer type, or pointer to member
function type:
If the argument is an overload set containing one or more function templates, the parameter is treated as a non-deduced context.
If the argument is an overload set (not containing function templates), trial argument deduction is attempted using each of the
members of the set. If deduction succeeds for only one of the
overload set members, that member is used as the argument value for
the deduction. If deduction succeeds for more than one member of the
overload set the parameter is treated as a non-deduced context.
So there you have it in all its glory.
The title is quoted from this SO answer. It is discussing using SFINAE to detect the existence of a member function with the given signature and points out a failing of the method in the accepted answer when dealing with inherited member functions. In particular, the explanation given is as follows
If you are not already wise to this gotcha, then a look at of the definition of std::shared_ptr<T> in the header will shed light. In that implementation, std::shared_ptr<T> is derived from a base class from which it inherits operator*() const. So the template instantiation SFINAE<U, &U::operator*> that constitutes "finding" the operator for U = std::shared_ptr<T> will not happen, because std::shared_ptr<T> has no operator*() in its own right and template instantiation does not "do inheritance".
This snag does not affect the well-known SFINAE approach, using "The sizeof() Trick", for detecting merely whether T has some member function mf (see e.g. this answer and comments).
Using the terminology from the answer, what is the difference between using T::mf as a template argument to instantiate a type vs having the compiler determine it through a template function argument deduction? What does "template instantiation does not do inheritance" mean? And lastly, why doesn't this affect simply checking for existence of a member, like here?
Minimized example:
struct A {
void a() const;
};
struct B : A {};
template<typename U, void (U::*)() const> struct SFINAE {};
template<typename U> void Test(SFINAE<U, &U::a>*) { }
int main(void)
{
Test<B>(0); // doesn't compile
return 0;
}
Demo.
The problem is that when B::a is inherited from A, the type of &B::a is actually "pointer to member of A" - and, while normally a pointer-to-member-of-base can be implicitly converted to pointer-to-member-of-derived, this conversion doesn't apply for non-type template arguments, per §14.3.2 [temp.arg.nontype]/p5:
The following conversions are performed on each expression used as a
non-type template-argument. If a non-type template-argument cannot
be converted to the type of the corresponding template-parameter
then the program is ill-formed.
[...]
For a non-type template-parameter of type pointer to member function,
if the template-argument is of type std::nullptr_t, the
null member pointer conversion (4.11) is applied; otherwise, no
conversions apply. If the template-argument represents a set of
overloaded member functions, the matching member function is selected
from the set (13.4).