尝试实现类型特征时,我对 SFINAE 的应用有什么问题?
What is wrong with my application of SFINAE when trying to implement a type trait?
我需要一个类型特征,它可以将枚举衰减到它们的基础类型,并且与 decay_t
对于所有其他类型的工作方式相同。我已经编写了以下代码,显然这不是 SFINAE 的工作方式。但这是我认为它应该工作的方式,那么这段代码到底有什么问题,我对 C++ 的理解有什么差距?
namespace detail {
template <typename T, std::enable_if_t<!std::is_enum_v<T>>* = nullptr>
struct BaseType {
using Type = std::decay_t<T>;
};
template <typename T, std::enable_if_t<std::is_enum_v<T>>* = nullptr>
struct BaseType {
using Type = std::underlying_type_t<T>;
};
}
template <class T>
using base_type_t = typename detail::BaseType<T>::Type;
MSVC中的错误完全看不懂:
'detail::BaseType': template parameter '__formal' is incompatible with
the declaration
在 GCC 中,情况要好一些 - 表示第二个模板参数的声明在两个 BaseType
模板之间不兼容。但根据我对 SFINAE 的理解,对于任何给定的 T,只有一个应该是可见的,而另一个应该是畸形的,这要归功于 enable_if
.
你用不同的参数声明相同的结构,这是被禁止的。
你可以通过部分专业化来做到这一点:
namespace detail {
template <typename T, typename Enabler = void>
struct BaseType {
using Type = std::decay_t<T>;
};
template <typename E>
struct BaseType<E, std::enable_if_t<std::is_enum_v<E>>>
{
using Type = std::underlying_type_t<E>;
};
}
BaseType
不是 partial-specialized,您只是重新声明它,并且由于 non-type 参数具有不同的类型,因此编译失败。你可能想做
#include <type_traits>
namespace detail {
template <typename T, bool = std::is_enum_v<T>>
struct BaseType;
template <typename T>
struct BaseType<T, false> {
using Type = std::decay_t<T>;
};
template <typename T>
struct BaseType<T, true> {
using Type = std::underlying_type_t<T>;
};
}
应用于 class 模板的 SFINAE 主要是关于在模板的部分专业化之间进行选择。您的代码段中的问题是看不到部分专业化。您定义了两个具有相同模板名称的主要 class 模板。那是重定义错误。
为了让它工作,我们应该重构特征实现之间的关系,例如它们专门化相同的模板。
namespace detail {
template <typename T, typename = void> // Non specialised case
struct BaseType {
using Type = std::decay_t<T>;
};
template <typename T>
struct BaseType<T, std::enable_if_t<std::is_enum_v<T>>> {
using Type = std::underlying_type_t<T>;
};
}
template <class T>
using base_type_t = typename detail::BaseType<T>::Type;
专业化为第二个参数提供了一个 void
类型(就像实例化主参数一样)。但是因为它是以一种“特殊方式”进行的,部分排序认为它更专业。当替换失败时(我们不传递枚举),主要成为后备。
您可以根据需要提供任意数量的此类特化,只要第二个模板参数始终为 void
,并且所有特化都具有互斥条件。
我需要一个类型特征,它可以将枚举衰减到它们的基础类型,并且与 decay_t
对于所有其他类型的工作方式相同。我已经编写了以下代码,显然这不是 SFINAE 的工作方式。但这是我认为它应该工作的方式,那么这段代码到底有什么问题,我对 C++ 的理解有什么差距?
namespace detail {
template <typename T, std::enable_if_t<!std::is_enum_v<T>>* = nullptr>
struct BaseType {
using Type = std::decay_t<T>;
};
template <typename T, std::enable_if_t<std::is_enum_v<T>>* = nullptr>
struct BaseType {
using Type = std::underlying_type_t<T>;
};
}
template <class T>
using base_type_t = typename detail::BaseType<T>::Type;
MSVC中的错误完全看不懂:
'detail::BaseType': template parameter '__formal' is incompatible with the declaration
在 GCC 中,情况要好一些 - 表示第二个模板参数的声明在两个 BaseType
模板之间不兼容。但根据我对 SFINAE 的理解,对于任何给定的 T,只有一个应该是可见的,而另一个应该是畸形的,这要归功于 enable_if
.
你用不同的参数声明相同的结构,这是被禁止的。
你可以通过部分专业化来做到这一点:
namespace detail {
template <typename T, typename Enabler = void>
struct BaseType {
using Type = std::decay_t<T>;
};
template <typename E>
struct BaseType<E, std::enable_if_t<std::is_enum_v<E>>>
{
using Type = std::underlying_type_t<E>;
};
}
BaseType
不是 partial-specialized,您只是重新声明它,并且由于 non-type 参数具有不同的类型,因此编译失败。你可能想做
#include <type_traits>
namespace detail {
template <typename T, bool = std::is_enum_v<T>>
struct BaseType;
template <typename T>
struct BaseType<T, false> {
using Type = std::decay_t<T>;
};
template <typename T>
struct BaseType<T, true> {
using Type = std::underlying_type_t<T>;
};
}
应用于 class 模板的 SFINAE 主要是关于在模板的部分专业化之间进行选择。您的代码段中的问题是看不到部分专业化。您定义了两个具有相同模板名称的主要 class 模板。那是重定义错误。
为了让它工作,我们应该重构特征实现之间的关系,例如它们专门化相同的模板。
namespace detail {
template <typename T, typename = void> // Non specialised case
struct BaseType {
using Type = std::decay_t<T>;
};
template <typename T>
struct BaseType<T, std::enable_if_t<std::is_enum_v<T>>> {
using Type = std::underlying_type_t<T>;
};
}
template <class T>
using base_type_t = typename detail::BaseType<T>::Type;
专业化为第二个参数提供了一个 void
类型(就像实例化主参数一样)。但是因为它是以一种“特殊方式”进行的,部分排序认为它更专业。当替换失败时(我们不传递枚举),主要成为后备。
您可以根据需要提供任意数量的此类特化,只要第二个模板参数始终为 void
,并且所有特化都具有互斥条件。