Should the decltype parameter in the template value parameter trigger the SFINAE context?

While experimenting with some constructs of template constraints, I found amazing behavior in Clang 3.7:

struct constraint_success {}; struct constraint_failure {}; template<bool> struct require_t {}; template<> struct require_t<true> { static constexpr constraint_success* result = nullptr; }; template<> struct require_t<false> { static constexpr constraint_failure* result = nullptr; }; template<bool Condition> constexpr auto require = require_t<Condition>::result; //A named dummy value, since we need a default constexpr constraint_success* required = nullptr; 

This decltype parameter launches the SFINAE context in my compiler:

 template<constraint_success* value> using constraint = decltype(value); 

Unlike:

 //template<constraint_success* value> //using constraint = constraint_success*; 

Example:

 //define custom constraints template<typename T> constexpr auto Pointer = require<std::is_pointer<T>::value>; template<typename T> constexpr auto NotPointer = require<!std::is_pointer<T>::value>; //constrain template parameters template<typename T, constraint<Pointer<T>> = required> void foo() { std::cout << "Hello, pointer!\n"; } template<typename T, constraint<NotPointer<T>> = required> void foo() { std::cout << "Hello, not pointer!\n"; } int main() { foo<int*>(); foo<int>(); return 0; } 

Is it required by standard, or is it a β€œgood” compiler?

Wandbox Link

+7
c ++ language-lawyer sfinae template-meta-programming c ++ 14
source share
1 answer

Alias ​​templates, such as constraint , are replaced in any template definition in which they are used. They are special: other templates are replaced after providing certain arguments.

So this announcement:

 template<typename T, constraint<Pointer<T>> = required> void foo(); 

roughly equivalent to this declaration (the difference is replacing a static_cast with an implicit conversion):

 template<typename T, decltype(static_cast<constraint_success*> (require_t<std::is_pointer<T>>::result)) = required> void foo(); 

This creates SFINAE if static_cast not valid.

The effect is useful and essentially allows you to emulate limitations from the function of upcoming concepts. However, this particular approach is rather confusing, and you are not actually using it.

The canonical approach of SFINAE is as follows:

 template< typename T > std::enable_if_t< std::is_pointer< T >::value > foo(); 

You can be a little cleaner and smarter and remove SFINAE from the return type - what you are going to do now:

 template< typename T, std::enable_if_t< std::is_pointer< T >::value > * = nullptr > void foo(); 

Given the variable templates in the TS knowledge base, this corresponds to a symbol-symbol with your example:

 template< typename T, std::enable_if_t< std::is_pointer_v< T > > * = nullptr > void foo(); // constraint < Pointer <T>> = required> 

The true Concepts-ish method is as follows:

 template< typename T, std::enable_if_t< std::is_pointer< T >::value > * = nullptr > using Pointer = T; template< typename T > void foo( Pointer<T> ); 
+8
source share

All Articles