std::underlying_type 没有发生 SFINAE
SFINAE not happening with std::underlying_type
下面带有可变参数模板的 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
的正确重载:
- 如果
std::begin(h)
是为 H
类型的 h
定义的,我想要
要选择的重载编号 1
- 如果
H
是一个 "integral type",我想要重载编号 2。
按原样工作。但是如果我为 enum 类型添加另一个重载(您可以尝试取消注释第三个重载),那么我得到:
error: only enumeration types have underlying types
我同意只有 enums 有一个基础类型,因此为什么不是第三个重载(std::underlying_type
)让 SFINAE-d 消失?
std::underlying_type
对 SFINAE 不友好。尝试访问非枚举类型的 std::underlying_type<T>::type
会导致未定义的行为(通常是硬错误),而不是替换失败。
在尝试访问其基础类型之前,您需要首先确定所讨论的类型是枚举类型。将此写成行将类似于 typename std::enable_if<std::is_enum<H>::value, std::underlying_type<H>>::type::type
。用这个可怕的混乱替换 return 类型中的 typename std::underlying_type<H>::type
,你会得到一个更可怕的混乱:)
如果你发现自己经常需要这样做 - 或者只是不想写 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
- 你可以写一个 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 */> {};
这是一个受 启发的解决方案,适用于我的用例:
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;
}
下面带有可变参数模板的 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
的正确重载:
- 如果
std::begin(h)
是为H
类型的h
定义的,我想要 要选择的重载编号 1 - 如果
H
是一个 "integral type",我想要重载编号 2。
按原样工作。但是如果我为 enum 类型添加另一个重载(您可以尝试取消注释第三个重载),那么我得到:
error: only enumeration types have underlying types
我同意只有 enums 有一个基础类型,因此为什么不是第三个重载(std::underlying_type
)让 SFINAE-d 消失?
std::underlying_type
对 SFINAE 不友好。尝试访问非枚举类型的 std::underlying_type<T>::type
会导致未定义的行为(通常是硬错误),而不是替换失败。
在尝试访问其基础类型之前,您需要首先确定所讨论的类型是枚举类型。将此写成行将类似于 typename std::enable_if<std::is_enum<H>::value, std::underlying_type<H>>::type::type
。用这个可怕的混乱替换 return 类型中的 typename std::underlying_type<H>::type
,你会得到一个更可怕的混乱:)
如果你发现自己经常需要这样做 - 或者只是不想写 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
- 你可以写一个 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 */> {};
这是一个受
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;
}