【问题标题】:SFINAE not happening with std::underlying_typeSFINAE 没有发生在 std::underlying_type 上
【发布时间】:2016-08-02 18:11:03
【问题描述】:

下面带有可变参数模板的 SFINAE 代码可以使用 clang 3.7.1、C++14 很好地编译:

#include <array>
#include <iostream>
#include <vector>
#include <cstdint>

enum class Bar : uint8_t {
    ay, bee, see
};

struct S {

static void foo() {}

// std::begin(h) is defined for h of type H
template<typename H, typename... T>
static typename std::enable_if<std::is_pointer<decltype(std::begin(std::declval<H>()))*>::value>::type 
foo(const H&, T&&... t) 
{ std::cout << "container\n"; foo(std::forward<T>(t)...); }

// H is integral
template<typename H, typename... T>
static typename std::enable_if<std::is_integral<typename std::remove_reference<H>::type>::value>::type 
foo(const H&, T&&... t) 
{ std::cout << "integer\n"; foo(std::forward<T>(t)...); }

// H is an enum with underlying type = uint8_t
/*
template<typename H, typename... T>
static typename std::enable_if<std::is_same<typename std::underlying_type<H>::type,uint8_t>::value>::type 
foo(const H&, T&&... t)
{ std::cout << "enum\n"; foo(std::forward<T>(t)...); }
*/
};


int main()
{
    S::foo(std::array<int,8>(), 5, 5L, std::vector<int>{}, 5L);
}

我希望基于 H 类型递归调用正确的 foo 重载:

  1. 如果为h 类型为H 定义了std::begin(h),我想要 选择 1 号过载
  2. 如果H 是“整数类型”,我想要重载号 2。

按原样工作。但是,如果我为 enum 类型添加另一个重载(您可以尝试取消注释第三个重载),那么我得到:

错误:只有枚举类型有底层类型

我同意只有 enums 有一个底层类型,因此为什么不是第三个重载(带有std::underlying_type)让 SFINAE-d 消失?

【问题讨论】:

  • 为什么不直接使用std::enable_if&lt;std::is_enum&lt;H&gt;::value&gt;::type?它compiles fine。您是否只针对 enums 的基础类型为 uint8_t?在这种情况下,您可以再添加 1 个条件 sizeof(H) == sizeof(uint8_t)。即std::is_enum&lt;H&gt;::value &amp;&amp; (sizeof(H) == sizeof(uint8_t))。上面的 ideone 示例对此进行了介绍。
  • @iammilind:很好的建议,谢谢

标签: c++ enums variadic-templates sfinae overload-resolution


【解决方案1】:

这是一个受T.C.'s solution 启发的解决方案,适用于我的用例:

template <typename T, bool = std::is_enum<T>::value>
struct relaxed_underlying_type {
    using type = typename std::underlying_type<T>::type;
};

template <typename T>
struct relaxed_underlying_type<T, false> {
    using type = T;
};

示例用法:

template <typename T>
struct UnwrapEnum {
    using type =
        typename std::conditional<
        std::is_enum<T>::value,
        typename relaxed_underlying_type<T>::type,
        T>
        ::type;
};

enum class MyEnum : int {};

class MyClass {};

int main() {
    UnwrapEnum<MyEnum>::type x;
    static_assert(std::is_same<decltype(x), int>::value);

    UnwrapEnum<MyClass>::type y;
    static_assert(std::is_same<decltype(y), MyClass>::value);

    return 0;
}

【讨论】:

    【解决方案2】:

    std::underlying_type 对 SFINAE 不友好。尝试访问非枚举类型的 std::underlying_type&lt;T&gt;::type 会导致未定义的行为(通常是硬错误),而不是替换失败。

    在尝试访问其基础类型之前,您需要先确定所讨论的类型是枚举类型。按照typename std::enable_if&lt;std::is_enum&lt;H&gt;::value, std::underlying_type&lt;H&gt;&gt;::type::type 的方式编写此内容。用这个可怕的混乱替换你的返回类型中的typename std::underlying_type&lt;H&gt;::type,你会得到一个更可怕的混乱:)

    如果你发现自己需要经常这样做——或者只是不想写typename std::enable_if&lt;std::is_same&lt;typename std::enable_if&lt;std::is_enum&lt;H&gt;::value, std::underlying_type&lt;H&gt;&gt;::type::type, uint8_t&gt;::value&gt;::type——你可以写一个对 SFINAE 友好的underlying_type:

    template<class T, bool = std::is_enum<T>::value>
    struct safe_underlying_type : std::underlying_type<T> {};
    template<class T>
    struct safe_underlying_type<T, false /* is_enum */> {};
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-15
      相关资源
      最近更新 更多