尝试实现类型特征时,我对 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.

Godbolt link

你用不同的参数声明相同的结构,这是被禁止的。

你可以通过部分专业化来做到这一点:

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

Demo

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,并且所有特化都具有互斥条件。