Using alias templates for sfinae: does the language allow it?

  • A+
Category:Languages

I have just discovered the following technique. It looks very close to one of proposed concepts syntax, works perfectly on Clang, GCC and MSVC.

template <typename T, typename = typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type> using require_rvalue = T&&;  template <typename T> void foo(require_rvalue<T> val); 

I tried to find it with search requests like "sfinae in type alias" and got nothing. Is there a name for this technique and does the language actually allows it?


The full example:

#include <type_traits>  template <typename T, typename = typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type> using require_rvalue = T&&;  template <typename T> void foo(require_rvalue<T>) { }  int main() {     int i = 0;     const int ic = 0;     foo(i);            // fail to compile, as desired     foo(ic);           // fail to compile, as desired     foo(std::move(i)); // ok     foo(123);          // ok } 

 


[...] does the language actually allows it?

Can't say anything about the name, but this seems to me to be a yes.

The relevant wording is [temp.alias]/2:

When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the type-id of the alias template.

and the sfinae rule, [temp.deduct]/8:

Only invalid types and expressions in the immediate context of the function type, its template parameter types, and its explicit-specifier can result in a deduction failure.

Taking an argument of type require_rvalue<T> does behave as if we substitute that alias, which either gives us a T&& or a substitution failure - and that substitution failure is arguably in the immediate context of the substitution and so is "sfinae-friendly" as opposed to being a hard error. Note that even though the defaulted type argument is unused, as a result of CWG 1558 (the void_t rule), we got the addition of [temp.alias]/3:

However, if the template-id is dependent, subsequent template argument substitution still applies to the template-id.

This ensures that we still substitute into the defaulted type argument to trigger the required substitution failure.

The second unsaid part of the question is whether this actually can behave as a forwarding reference. The rule there is in [temp.deduct.call]/3:

A forwarding reference is an rvalue reference to a cv-unqualified template parameter that does not represent a template parameter of a class template (during class template argument deduction ([over.match.class.deduct])). If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.

Is an alias template with one template parameter whose associated type is an rvalue reference to its cv-unqualified template parameter considered a forwarding reference? Well, [temp.alias]/2 says that require_rvalue<T> is equivalent to T&&, and T&& is the right thing. So arguably... yeah.

And all the compilers treat it as such, which is certainly a nice validation to have.


Although, note the existence of CWG 1844 and the lack of actual definition for immediate context, and the example there which also relies upon a substitution failure from a defaulted argument - which the issue states has implementation divergence.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: