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

Otr*_*gal 5 c++ templates partial-specialization sfinae enable-if

在过去的几个小时里,我一直在努力解决非常奇怪的问题(在我刚接触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;
}
Run Code Online (Sandbox Code Playgroud)

当我尝试对其进行编译时,我得到一个关于3个模板参数而不是2个模板参数的错误。然后,经过一番尝试,我决定将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;
}
Run Code Online (Sandbox Code Playgroud)

现在,神奇地一切正常。但是我的问题是为什么?编译器没有看到我正在尝试在类定义中内联同样依赖于3个模板参数的成员函数吗?还是让我们扭转这个问题:如果它在A定义内起作用,为什么它在外部不起作用?有什么区别?仍然没有3个参数,比哪个类A的模板参数需要多1个参数吗?

另外,为什么仅当我将第三个参数设为非类型1而不是类型1时才起作用?注意,我实际上创建了一个由enable_if返回的类型的指针,并将其分配为默认值nullptr,但是我看到我不能像在其他SO论坛文章中那样将其作为类型参数保留在那里。

非常感谢,谢谢!!!

Jus*_*ica 6

这将是因为在模板类模板函数有2套模板参数,而不是一个。因此,“正确”的形式是:

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";
}
Run Code Online (Sandbox Code Playgroud)

在这里查看它的实际效果。

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

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


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

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

    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";
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

    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";
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. A<T, N>::g()内部定义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";
        }
    };
    
    Run Code Online (Sandbox Code Playgroud)

    在这种情况下,由于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";
        }
    // ...
    
    Run Code Online (Sandbox Code Playgroud)

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


既然我们已经涵盖了这一点,我应该指出 SFINAE 只涉及直接模板参数。所以,为了g()使用 SFINAE with N,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";
}
Run Code Online (Sandbox Code Playgroud)

如果这样做,我们可以通过让中间人完成繁重的工作来进一步简化事情。

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

// ...
Run Code Online (Sandbox Code Playgroud)

在这里查看它的实际效果。