SFINAE:检测成员变量的存在不适用于g ++

时间:2019-03-13 12:37:52

标签: c++ g++ sfinae

我正在尝试将this answer中用于检测类是否具有成员变量xthis answer的方法结合起来,以根据使用{{1}的方法来选择不同的实现}。

基本上,我想编写一个特征类,给定类型为enable_if,它可以访问成员T(如果存在),否则提供默认值。

以下代码无法在g ++上编译:(Compiler Explorer

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

首先应该检测error: 'x' is not a member of 'WithoutX' struct HasX <T, decltype((void) T::x)> : std::true_type { }; 是否是成员的部分。不过,奇怪的是,如果我不注释第二行,它本身就实例化了x,则g ++编译时不会出错(Compiler Explorer)。

clang和msvc均可在Compiler Explorer上编译而不会出现问题。

这是怎么了?

2 个答案:

答案 0 :(得分:3)

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 :(得分:1)

关于以下内容的切换注释:

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

确实可以解决问题Demo