g ++ и clang ++ различное поведение с шаблоном переменной и SFINAE

Другой вопрос типа «кто прав между 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>(), "!" );
}

6

Решение

Давайте проанализируем, что здесь происходит:

Мое первоначальное предположение состоит в том, что это похоже на неверную интерпретацию лязга. Невозможно вернуться в дерево разрешения, когда 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 сам по себе является хакерским компилятором и может вести себя странным образом, когда компиляторы пытаются быть «слишком умными»

0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]