SFINAE not happening with std::underlying_type

你离开我真会死。 提交于 2019-12-05 11:26:54

问题


Below SFINAE code with variadic templates compiles nicely using 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);
}

I want the correct overload of foo to be called recursively, based on the type H:

  1. if std::begin(h) is defined for an h of type H, I want the overload number 1 to be chosen
  2. if H is an "integral type", I want overload number 2.

This works as it is. But if I add another overload for enum types (you can try to un-comment the third overload), then I get:

error: only enumeration types have underlying types

I agree that only enums got an underlying type, hence why is Not the third overload (with std::underlying_type) get SFINAE-d away?


回答1:


std::underlying_type is not SFINAE friendly. Attempting to access std::underlying_type<T>::type for a non-enumeration type results in undefined behavior (often a hard error), not substitution failure.

You need to ascertain that the type at issue is an enumeration type first, before attempting to access its underlying type. Writing this in line would be something along the lines of typename std::enable_if<std::is_enum<H>::value, std::underlying_type<H>>::type::type. Replacing the typename std::underlying_type<H>::type in your return type with this hideous mess and you get an even more hideous mess that works :)

If you find yourself needing to do this often - or just don't want to write typename std::enable_if<std::is_same<typename std::enable_if<std::is_enum<H>::value, std::underlying_type<H>>::type::type, uint8_t>::value>::type - you can write a SFINAE-friendly 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 */> {};



回答2:


Here's a solution inspired from T.C.'s solution that worked for my use case:

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

Example Usage:

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



来源:https://stackoverflow.com/questions/36568050/sfinae-not-happening-with-stdunderlying-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!