为什么 SFINAE (enable_if) 从 class 定义内部工作,而不是从外部工作

why SFINAE (enable_if) works from inside class definition but not from outside

过去几个小时我一直在努力解决一个非常奇怪的问题(在用 SFINAE 解决了 5-6 个其他问题之后,因为我是新手)。基本上在下面的代码中,我想让 f() 用于所有可能的模板实例化,但是 g() 仅在 N == 2:

时可用
#include <type_traits>
#include <iostream>

template<typename T, int N>
class A
{
public:
    void f(void);
    void g(void);
};

template<typename T, int N>
inline void A<T, N>::f()
{
    std::cout << "f()\n";
}

template<typename T, int N, typename std::enable_if<N == 2, void>::type* = nullptr>
inline void A<T, N>::g()
{
    std::cout << "g()\n";
}

int main(int argc, char *argv[])
{
    A<float, 2> obj;
    obj.f();
    obj.g();

    return 0;
}

当我尝试编译它时,我得到一个关于有 3 个而不是两个模板参数的错误。然后,经过一些试验,我决定将 g() 的定义移动到 A 本身的定义中,如下所示:

#include <type_traits>
#include <iostream>

template<typename T, int N>
class A
{
public:
    void f(void);

    template<typename t = T, int n = N, typename std::enable_if<N == 2, void>::type* = nullptr>
    void g()
    {
        std::cout << "g()\n";
    }
};

template<typename T, int N>
inline void A<T, N>::f()
{
    std::cout << "f()\n";
}

int main(int argc, char *argv[])
{
    A<float, 2> obj;
    obj.f();
    obj.g();

    return 0;
}

现在,奇迹般地一切正常。但我的问题是为什么?编译器没有看到我在 class 定义中试图内联一个也依赖于 3 个模板参数的成员函数吗?或者我们反过来问:如果它在 A 的定义内有效,为什么它在外部不起作用?哪里不一样?不是还有 3 个参数,比 class A 需要的模板参数多 +1 吗?

另外,为什么它只在我将第三个参数设为非类型参数而不是类型参数时才起作用?请注意,我实际上创建了一个由 enable_if 返回的类型的指针,并为其分配了一个默认值 nullptr,但我发现我不能像我在这里看到的其他 SO 论坛帖子那样将它作为类型参数留在那里.

非常感谢,谢谢!!!

在第一个代码段中,模板参数是 A 并且您使用额外参数重新声明它(这是一个错误)。
而且,sfinae表达式涉及class模板或函数模板,例子中不是这样。

在第二个代码片段中,模板参数是 g,现在它是一个正确应用 sfinae 表达式的成员函数模板。


它遵循一个工作版本:

#include <type_traits>
#include <iostream>

template<typename T, int N>
class A
{
public:
    void f(void);

    template<int M = N>
    std::enable_if_t<M==2> g();
};

template<typename T, int N>
inline void A<T, N>::f()
{
    std::cout << "f()\n";
}

template<typename T, int N>
template<int M>
inline std::enable_if_t<M==2> A<T, N>::g()
{
    std::cout << "g()\n";
}

int main(int argc, char *argv[])
{
    A<float, 2> obj;
    obj.f(); // ok
    obj.g(); // ok (N==2)

    A<double,1> err;
    err.f(); // ok
    //err.g(); invalid (there is no g())

    return 0;
}

请注意,non-type 参数必须位于 sfinae 表达式的实际上下文中,后者才能起作用。
因此,template<int M = N> 是强制性的。

其他解决方案也适用。
例如,您可以使用导出 f 的基础 class 和具有添加 g.

的完全专业化的派生模板 class

那是因为模板化 class 中的模板化函数具有 两套 模板参数集,而不是一套。因此 "correct" 形式是:

template<typename T, int N>
class A
{
public:
    void f(void);

    template<typename std::enable_if<N == 2, void>::type* = nullptr>
    void g(void);
};

template<typename T, int N>                                            // Class template.
template<typename std::enable_if<N == 2, void>::type* /* = nullptr */> // Function template.
inline void A<T, N>::g()
{
    std::cout << "g()\n";
}

查看实际效果 here

[请注意,这 实际上 不正确,原因在此答案的底部进行了解释。如果 N != 2.]

就会崩溃

如果您愿意,请继续阅读以获取解释。


还在我身边吗?好的。让我们检查每种情况,好吗?

  1. A 外定义 A<T, N>::g():

    template<typename T, int N>
    class A
    {
    public:
        void f(void);
        void g(void);
    };
    
    template<typename T, int N, typename std::enable_if<N == 2, void>::type* = nullptr>
    inline void A<T, N>::g()
    {
        std::cout << "g()\n";
    }
    

    在这种情况下,A<T, N>::g() 的模板声明与 A 的模板声明不匹配。因此,编译器会发出错误。此外,g() 本身不是模板化的,因此在不更改 A 的定义的情况下,无法将模板拆分为 class 模板和函数模板。

    template<typename T, int N>
    class A
    {
    public:
        void f(void);
    
        // Here...
        template<typename std::enable_if<N == 2, void>::type* = nullptr>
        void g(void);
    };
    
    // And here.
    template<typename T, int N>                                            // Class template.
    template<typename std::enable_if<N == 2, void>::type* /* = nullptr */> // Function template.
    inline void A<T, N>::g()
    {
        std::cout << "g()\n";
    }
    
  2. 定义 A<T, N>::g() inside A:

    template<typename T, int N>
    class A
    {
    public:
        void f(void);
    
        template<typename t = T, int n = N, typename std::enable_if<N == 2, void>::type* = nullptr>
        void g()
        {
            std::cout << "g()\n";
        }
    };
    

    在这种情况下,由于 g() 是内联定义的,因此它隐式具有 A 的模板参数,无需手动指定它们。因此,g()实际上是:

    // ...
        template<typename T, int N>
        template<typename t = T, int n = N, typename std::enable_if<N == 2, void>::type* = nullptr>
        void g()
        {
            std::cout << "g()\n";
        }
    // ...
    

在这两种情况下,为了让 g() 拥有自己的模板参数,同时作为模板化 class 的成员,函数模板参数必须与 class 分开模板参数。否则,函数的 class 模板将不匹配 class'.


既然我们已经介绍过了,我应该指出 SFINAE 只涉及 立即 模板参数。因此,为了让 g()N 一起使用 SFINAE,N 需要作为其模板参数;否则,如果您尝试调用 A<float, 3>{}.g(),则会出现错误。如有必要,这可以通过中介来完成。

此外,您需要提供 g() 的版本,可以在 N != 2 时调用。这是因为 SFINAE 仅在至少存在一个有效版本的函数时才适用;如果无法调用 g() 版本,则会发出错误并且不会执行任何 SFINAE。

template<typename T, int N>
class A
{
public:
    void f(void);

    // Note the use of "MyN".
    template<int MyN = N, typename std::enable_if<MyN == 2, void>::type* = nullptr>
    void g(void);

    // Note the "fail condition" overload.
    template<int MyN = N, typename std::enable_if<MyN != 2, void>::type* = nullptr>
    void g(void);
};

template<typename T, int N>
template<int MyN /*= N*/, typename std::enable_if<MyN == 2, void>::type* /* = nullptr */>
inline void A<T, N>::g()
{
    std::cout << "g()\n";
}

template<typename T, int N>
template<int MyN /*= N*/, typename std::enable_if<MyN != 2, void>::type* /* = nullptr */>
inline void A<T, N>::g()
{
    std::cout << "()g\n";
}

如果这样做,我们可以进一步简化事情,让中介来做繁重的工作。

template<typename T, int N>
class A
{
public:
    void f(void);

    template<bool B = (N == 2), typename std::enable_if<B, void>::type* = nullptr>
    void g(void);

    template<bool B = (N == 2), typename std::enable_if<!B, void>::type* = nullptr>
    void g(void);
};

// ...

查看实际效果 here