7

Экспериментируя с некоторым шаблоном ограничения конструкции, я обнаружил удивительное поведение в Clang 3.7:Должен ли decltype в параметре значения шаблона запускать контекст SFINAE?

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; 

Это decltype вызывает контекст SFINAE в моем компиляторе:

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

В противоположность:

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

Пример:

//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; 
} 

Это требуется по стандарту, или это «удачный» компилятор?

Wandbox link

ответ

8

алиасов шаблоны, такие как constraint подставляются в любом определении шаблона они используются в Они особенные таким образом:. Другие шаблоны заменяются один раз определенные аргументы поставляются.

Таким образом, эта декларация:

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

примерно соответствует этой декларации (разница в том, замещение static_cast для неявного преобразования):

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

Это производит SFINAE, когда static_cast является инвалид.

Эффект полезен и по существу позволяет вам эмулировать ограничения из функции предстоящих концепций. Однако этот конкретный подход довольно запутан, и вы не используете его.

Канонический SFINAE подход заключается в следующем:

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

Вы можете быть немного более чистым и умным, и удалить SFINAE от типа возвращаемого значения - то, что вы, кажется, в настоящее время собирается на:

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

Учитывая шаблоны переменных в библиотеке Основе TS, это соответствует символу-для-символа с вашим примером:

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

// constraint  < Pointer   <T>>  = required> 

Действительно концептуально-иш-способ выглядит так:

template< typename T, std::enable_if_t< std::is_pointer<T>::value > * = nullptr > 
using Pointer = T; 

template< typename T > 
void foo(Pointer<T>); 
Смежные вопросы