为什么这种替换失败会再次产生错误?

Why does this substitution failure create an error, again?

我刚才问了一个问题,为什么 std::enable_if<false> 不能在 SFINAE 上下文中使用,如:

template <typename T, typename DEFAULTVOID = void>
struct TemplatedStruct {};

template <typename T>
struct TemplatedStruct<T, std::enable_if_t<false>> {}; // enable_if expression
// isn't dependent on template type, is always false and so is an error

但是在下面的示例中它依赖于模板参数,但这也会产生错误:

#include <type_traits>

template <typename value_t_arg>
struct underlyingtype 
{
    static inline constexpr bool bIsIntegralType = 
        std::is_integral_v<value_t_arg>;

    template <typename T, typename DEFAULTVOID = void>
    struct IsSpecialType {
        static inline constexpr bool bIsSpecialType = false;
    };
    
    template <typename T>
    struct IsSpecialType<T, std::enable_if_t<bIsIntegralType>> {
        static inline constexpr bool bIsSpecialType = true;
    };
    
    // This also creates an error, this is essentially the same as above
    template <typename T>
    struct IsSpecialType<T, std::enable_if_t<std::is_integral_v<value_t_arg>>> {
        static inline constexpr bool bIsSpecialType = true;
    };


};

int main()
{
    underlyingtype<int> g1; // Works
    underlyingtype<double> g2; // std::enable_if_t<false, void>:
                               // Failed to specialize alias template
}

在使用 std::enable_if_t<false> 的第一种情况下,无论我实例化什么,它都无法编译。然而,在另一种情况下,underlyingtype<int> g1; 可以工作,而当我用 double 实例化它时,它无法编译,这让我认为它们是两个不同的问题。

编辑:我应该提一下,这无法使用 Visual Studio Community 2019 16.9.3.

进行编译

// Failed to specialize alias template

首先,您的代码中没有别名模板。¹您只是将 bIsIntegralType 定义为与 std::is_integral_v<value_t_arg> 完全相同的东西,它是固定的(到 falsetrue) 一旦 underlyingtype 的实例化发生。

因此,两个专业

    template <typename T>
    struct IsSpecialType<T, std::enable_if_t<bIsIntegralType>> {
        static inline constexpr bool bIsSpecialType = true;
    };

    // This also creates an error, this is essentially the same as above
    template <typename T>
    struct IsSpecialType<T, std::enable_if_t<std::is_integral_v<value_t_arg>>> {
        static inline constexpr bool bIsSpecialType = true;
    };

是同一件事,因此 clang 说

Class template partial specialization 'IsSpecialType<T>' cannot be redeclared

这与 value_t_arg 您传递给 underlyingtype 的内容无关。

当删除两个相同的特化中的任何一个时,关于 underlyingtype<int> g1; 的代码是可以的,但在尝试实例化 underlyingtype<double> 时它仍然无效,因为 value_t_arg 被“阻止” " 到 double 在这种情况下,这使得 bIsIntegralType 只是一个 false 编译时值,这反过来意味着你正在传递一个永远永远的 falsestd::enable_if_v.

换句话说,当您请求 underlyingtype<double> 时,编译器开始用 value_t_arg = double 实例化 class underlyingtype;此时编译器甚至没有查看 IsSpecialType,但它知道 bIsIntegralType == false,这使得 IsSpecialType 的代码根据 的特化无效。


(¹) alias template 是模板化的类型别名,

template <typename T>
using new_name = old_name<T>;

而在您的代码中根本没有 using,因此不可能有类型别名,更不用说别名模板了。


根据这个问题和上一个问题,您似乎正在尝试进入 SFINAE 和模板元编程。如果我可以给你一个建议,学习它的一个好方法是阅读和理解 Boost.Hana 库是如何工作的。那里有 很多 的 TMP 和 SFINAE,但是代码的质量很高(恕我直言),代码本身也有很好的文档记录,而且,因此,可以理解(显然这需要时间)。

考虑这一行:

std::cout << underlyingtype<double>::IsSpecialType<char>::bIsSpecialType << "\n";

我们应该如何解读它?

  • underlyingtype 是模板。
  • underlyingtype<double>不是模板,是类型,underlyingtype.
  • 的具体实例化
  • underlyingtype<double>::IsSpecialType是一个模板,一个(非模板)class类型的成员underlyingtype<double>这个模板只有一个参数T.
  • underlyingtype<double>::IsSpecialType<char>是前面模板的实例化。

现在,在实例化模板时, 参数将替换为实际参数。未能执行此类替换不是错误。在 underlyingtype<double>::IsSpecialType 的情况下,参数是 T。但是 std::enable_if_t<std::is_integral_v<value_t_arg>>> 不依赖于 T,因此不会发生替换。