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

时间:2017-01-27 22:31:30

标签: c++ templates sfinae enable-if partial-specialization

非常奇怪的问题我在过去的几个小时里一直在努力(在解决了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个模板参数而不是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;
}

现在,神奇地一切正常。但我的问题是为什么?编译器是否认为在类定义中我试图内联一个也取决于3个模板参数的成员函数?或者让我们反驳这个问题:如果它在A的定义范围内有效,为什么它不在外面工作?差异在哪里? Aren还有3个参数,比模板参数所需的类A多+1个?

另外,为什么只有当我将第3个参数设为非类型参数而不是类型1时,它才有效?请注意,我实际上创建了一个由enable_if返回的类型的指针,并为其指定了一个默认值nullptr,但我发现我不能将其作为类型参数保留在那里,就像我在这里看到的其他SO论坛帖子一样。 / p>

非常感谢,谢谢!

2 个答案:

答案 0 :(得分:4)

这是因为模板化类中的模板化函数具有两个模板参数集,而不是一个。因此,“正确”形式是:

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

    在这种情况下,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";
    }
    
  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";
        }
    };
    

    在这种情况下,由于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";
        }
    // ...
    
  3. 在这两种情况下,要使g()拥有自己的模板参数,在成为模板化类的成员时,必须将函数模板参数与类模板参数分开。否则,函数的类模板将与类'。

    不匹配

    现在我们已经介绍过了,我应该指出SFINAE只关注立即模板参数。因此,g()要将SFINAE与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";
    }
    

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

    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中查看。

答案 1 :(得分:3)

在第一个代码段中,模板参数为A,您将使用额外参数重新声明它(这是一个错误)。
此外,sfinae表达式涉及类模板或函数模板,但在示例中并非如此。

在第二个代码段模板中,参数属于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;
}

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

其他解决方案也适用 例如,您可以使用导出f的基类和具有添加g的完整特化的派生模板类。

相关问题