SFINAE: Обнаружение существования переменной-члена не работает на g ++ - PullRequest
5 голосов
/ 13 марта 2019

Я пытаюсь объединить подходы, использованные в этом ответе для определения, имеет ли класс переменную-член x с этим ответом , чтобы выбрать различные реализации в зависимости от того, что используется enable_if.

По сути, я хочу написать класс характеристик, который, учитывая тип T, обеспечивает доступ к члену T::x, если он существует, и в противном случае предоставляет значение по умолчанию.

Следующий код не компилируется на g ++: ( Проводник компилятора )

#include <iostream>
#include <type_traits>

// classes with / without x member
struct WithX { static constexpr int x = 42; };
struct WithoutX {};

// trait to detect x
template <typename T, typename = void>
struct HasX : std::false_type { };

template <typename T>
struct HasX <T, decltype((void) T::x)> : std::true_type { };

// trait to provide default for x
template <typename T>
struct FooTraits
{
    template <bool enable = HasX<T>::value>
    static constexpr std::enable_if_t< enable, size_t> x() { return T::x; }
    template <bool enable = HasX<T>::value>
    static constexpr std::enable_if_t<!enable, size_t> x() { return 1; }

};


int main() {
    std::cout << HasX<WithX>::value << std::endl;
    // Uncomment the following line to make this compile with g++
    //std::cout << HasX<WithoutX>::value << std::endl;
    std::cout << FooTraits<WithoutX>::x() << std::endl;
}

g ++ выдает сообщения об ошибках,

error: 'x' is not a member of 'WithoutX'
  struct HasX <T, decltype((void) T::x)> : std::true_type { };

в части, которая должна определить, является ли x членом в первую очередь. Любопытно, что если я раскомментирую вторую и последнюю строку, которая сама создает экземпляр HasX<WithoutX>::value, g ++ компилируется без ошибок ( Проводник компилятора ).

Компиляция clang и msvc без проблем компилируется в Compiler Explorer.

Что здесь не так?

Ответы [ 2 ]

3 голосов
/ 13 марта 2019

SFINAE работают только в непосредственном контексте.Другими словами, если компилятор может заранее увидеть, что в объявлении есть проблема, тогда это должна быть ошибка.

При создании экземпляра класса компилятор попытается разрешить все, что может.Так что это:

template<bool enable = HasX<T>::value>
....

Это не зависит от контекста функции.Это может быть создано именно тогда, когда создается экземпляр FooTraits.

Другими словами, это назначение может быть рассчитано заранее, как если бы вы переместили его в область видимости класса.

В вашемВ этом случае компилятору нечего делать при подстановке.

Исправление состоит в том, что:

template <typename U = T, bool enable = HasX<U>::value>
static constexpr std::enable_if_t< enable, size_t> x() { return T::x; }
template <typename U = T, bool enable = HasX<U>::value>
static constexpr std::enable_if_t<!enable, size_t> x() { return 1; }

Теоретически U может быть совершенно другого типа.U непосредственно перед созданием функции, а T - нет.

1 голос
/ 14 марта 2019

Факт переключения комментариев:

//std::cout << HasX<WithoutX>::value << std::endl;

действительно хороший признак ошибки gcc.

Кажется, у gcc есть проблема с вашей формой:

template <typename T>
struct HasX <T, decltype((void) T::x)> : std::true_type {};

Более типичным способом является использование std::void_t:

template <typename T>
struct HasX <T, std::void_t<decltype(T::x)>> : std::true_type {};

, которая действительно решает проблему Демо .

...