为什么SFINAE(enable_if)从类内部定义而不是外部工作 [英] why SFINAE (enable_if) works from inside class definition but not from outside

查看:77
本文介绍了为什么SFINAE(enable_if)从类内部定义而不是外部工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在过去的几个小时里,我一直在努力解决一个非常奇怪的问题(在我刚接触SFINAE并解决了5-6个其他问题之后).基本上,在以下代码中,我希望f()可用于所有可能的模板实例化,但是g()仅在N == 2时可用:

Very weird problem I've been struggling with for the past few hours (after solving 5-6 other issues with SFINAE as I'm new to it). Basically in the following code I want to have f() working for all possible template instantiations, but have g() available only when 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本身的定义内,如下所示:

When I try to compile it I get an error about having 3 template parameters instead of two. Then, after some trials, I've decided to move the definition of g() inside the definition of A itself, like this:

#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的定义内起作用,为什么它不能在外部起作用?有什么区别?还没有3个参数,比A类的模板参数所需的参数多+1吗?

Now, magically everything works. But my question is WHY? Doesn't the compiler see that inside the class definition I'm trying to inline a member function that also depends on 3 template parameters? Or let's reverse the question: if it works inside A's definition, why doesn't it work outside? Where's the difference? Aren't there still 3 parameters, which is +1 more than what class A needs for its template parameters?

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

Also, why does it only work when I'm making the 3rd parameter a non-type one and not a type one? Notice I actually make a pointer of the type returned by enable_if and assign it a default value of nullptr, but I see I can't just leave it there as a type parameter like in other SO forum posts I see around here.

非常感谢,谢谢!!!

Appreciate it so much, thank you!!!

推荐答案

那是因为模板化类中的模板化函数具有两组模板参数集,而不是一组.因此,正确"形式为:

That would be because a templated function in a templated class has two sets of template parameters, not one. The "correct" form is thus:

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

此处看到它.

[请注意,实际上这不是正确,原因是此答案底部的原因.如果N != 2,它将中断.]

[Note that this isn't actually correct, for a reason explained at the bottom of this answer. It'll break if N != 2.]

如果您愿意,请继续阅读以得到解释.

Continue reading for an explanation, if you so desire.

还是和我在一起吗?好的.让我们检查每种情况,对吗?

Still with me? Nice. Let's examine each situation, shall we?

  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定义的情况下,无法将模板分为类模板和功能模板.

In this case, A<T, N>::g()'s template declaration doesn't match A's template declaration. Therefore, the compiler emits an error. Furthermore, g() itself isn't templated, so the template can't be split into a class template and a function template without changing A's definition.

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

  • A内定义A<T, N>::g():

  • Defining 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()实际上是:

    In this case, since g() is defined inline, it implicitly has A's template parameters, without needing to specify them manually. Therefore, g() is actually:

    // ...
        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()拥有自己的模板参数,同时又是模板化类的成员,则必须将功能模板参数与类模板参数分开.否则,该函数的类模板将与该类不匹配.

    In both cases, for g() to have its own template parameters, while being a member of a templated class, the function template parameters have to be separated from the class template parameters. Otherwise, the function's class template wouldn't match the class'.

    现在我们已经讨论了这一点,我应该指出,SFINAE仅涉及立即模板参数.因此,为了使g()N一起使用SFINAE,必须将N作为其模板参数.否则,如果尝试调用,则会出现错误,例如A<float, 3>{}.g().如有必要,可以通过中介来完成.

    Now that we've covered that, I should point out that SFINAE only concerns immediate template parameters. So, for g() to use SFINAE with N, N needs to be its template parameter; otherwise, you'd get an error if you tried to call, for example, A<float, 3>{}.g(). This can be accomplished with an intermediary, if necessary.

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

    Additionally, you'll need to provide a version of g() that can be called when N != 2. This is because SFINAE is only applicable if there's at least one valid version of the function; if no version of g() can be called, then an error will be emitted and no SFINAE will be performed.

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

    如果这样做,我们可以通过让中介机构承担繁重的工作来进一步简化事情.

    If doing this, we can further simplify things, by having the intermediary do the heavy lifting.

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

    此处中查看它.

    这篇关于为什么SFINAE(enable_if)从类内部定义而不是外部工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆