Другой вопрос типа «кто прав между g ++ и clang ++?» для C ++ стандартных гуру.
Предположим, что мы хотим применить SFINAE к шаблону переменной, чтобы включить переменную, только если тип шаблона удовлетворяет определенному условию.
Например: включить bar
если (и только если) тип шаблона имеет foo()
метод с заданной подписью.
Использование SFINAE через дополнительный тип шаблона со значением по умолчанию
template <typename T, typename = decltype(T::foo())>
static constexpr int bar = 1;
работает как для g ++, так и для clang ++, но имеет проблему: может быть взломан путем объяснения второго типа шаблона
Так
int i = bar<int>;
выдает ошибку компиляции где
int i = bar<int, void>;
скомпилировать без проблем.
Итак, из глубины моего незнания о SFINAE я попытался включить / отключить тип той же переменной:
template <typename T>
static constexpr decltype(T::foo(), int{}) bar = 2;
Сюрприз: это работает (компилируется) для g ++, но clang ++ не принимает его и выдает следующую ошибку
tmp_003-14,gcc,clang.cpp:8:30: error: no member named 'foo' in 'without_foo'
static constexpr decltype(T::foo(), int{}) bar = 2;
~~~^
Вопрос, как обычно, таков: кто прав? g ++ или clang ++?
Другими словами: в соответствии со стандартом C ++ 14 SFINAE может использоваться поверх типа шаблона переменной?
Ниже приведен полный пример игры с
#include <type_traits>
// works with both g++ and clang++
//template <typename T, typename = decltype(T::foo())>
//static constexpr int bar = 1;
// works with g++ but clang++ gives a compilation error
template <typename T>
static constexpr decltype(T::foo(), int{}) bar = 2;
struct with_foo
{ static constexpr int foo () { return 0; } };
struct without_foo
{ };
template <typename T>
constexpr auto exist_bar_helper (int) -> decltype(bar<T>, std::true_type{});
template <typename T>
constexpr std::false_type exist_bar_helper (...);
template <typename T>
constexpr auto exist_bar ()
{ return decltype(exist_bar_helper<T>(0)){}; }
int main ()
{
static_assert( true == exist_bar<with_foo>(), "!" );
static_assert( false == exist_bar<without_foo>(), "!" );
}
Давайте проанализируем, что здесь происходит:
Мое первоначальное предположение состоит в том, что это похоже на неверную интерпретацию лязга. Невозможно вернуться в дерево разрешения, когда bar
не решен должным образом.
Во-первых, чтобы убедиться, что проблема в bar
, мы можем это сделать:
template <typename T>
constexpr auto exist_bar_helper(int) -> decltype(void(T::foo()), std::true_type{});
Он работает правильно (SFINAE делает свою работу).
Теперь давайте изменим ваш код, чтобы проверить, будут ли вложенные неудачные разрешения обернуты внешним контекстом SFINAE. После изменения bar
быть функцией:
template <typename T>
static constexpr decltype(void(T::foo()), int{}) bar();
Это все еще работает правильно, круто. Тогда я бы предположил, что любое неправильное разрешение внутри нашего decltype
вернусь назад и разрешим функцию для возврата к SFINAE (std :: false_type) … но нет.
Это то, что делает GCC:
exist_bar -> exists_bar_helper -> bar (woops) -> no worries, i have alternatives
-> exists_bar_helper(...) -> false
Вот что делает CLANG:
exist_bar -> exists_bar_helper -> bar (woops) // oh no
// I cannot access that var, this is unrecoverable error AAAAAAAA
Он относится к этому настолько серьезно, что забывает о запасном варианте в верхнем контексте.
Короче говоря: не SFINAE для шаблонных переменных, SFINAE сам по себе является хакерским компилятором и может вести себя странным образом, когда компиляторы пытаются быть «слишком умными»
Других решений пока нет …