【问题标题】:SFINAE: Detecting existence of member variable does not work on g++SFINAE:检测成员变量的存在在 g++ 上不起作用
【发布时间】:2019-03-13 12:37:52
【问题描述】:

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

基本上,我想编写一个 trait 类,给定类型 T,如果成员 T::x 存在,则提供对它的访问,否则提供默认值。

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

#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&lt;WithoutX&gt;::value 本身,g++ 编译没有错误 (Compiler Explorer)。

clang 和 msvc 在 Compiler Explorer 上编译都没有问题。

这里有什么问题?

【问题讨论】:

  • 您可以使用:std::void_t&lt;decltype(T::x)&gt;

标签: c++ g++ sfinae


【解决方案1】:

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 不是。

【讨论】:

  • 该建议有效,谢谢。那么哪个编译器错了,g++还是clang/msvc?
  • 问题不在FooTraits 中,因为在两个编译器Demo 中使用void_t 修复HasX
【解决方案2】:

事实上,切换评论:

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

确实是gcc bug的好兆头。

您的表单似乎 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的问题。

【讨论】:

  • 有趣。此变体适用于 gcc 5+,但在 4.9.4 中失败(有和没有注释行),它根据注释行编译。
  • 该问题似乎与CWG1558有关。使用更复杂的void_t 定义,如此处en.cppreference.com/w/cpp/types/void_t 所述,它也适用于该 gcc。
猜你喜欢
  • 2011-03-23
  • 1970-01-01
  • 2016-06-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-05
相关资源
最近更新 更多