为什么'enable_if'不能在此处禁用此声明

Saj*_*jal 3 c++ type-traits enable-if variadic-templates c++14

#include<string>
#include<type_traits>

template<typename... Args>
class C {
public:
    void foo(Args&&... args) {      

    }

    template<typename = std::enable_if_t<(0 < sizeof...(Args))>>
    void foo(const Args&... args) {     

    }
};

int main() {
    C<> c;
    c.foo();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

上面的代码按预期的方式(由我:))工作,并void foo(Args&&... args)msvc 2015中在运行时调用,但相同的代码甚至无法在gcc 7.3clang 6.0.0中编译,并出现错误:

错误:“ std :: enable_if”中没有名为“ type”的类型;'enable_if'不能用于禁用此声明

我想了解上述代码有什么问题,如何解决?

Kla*_*aus 7

SFINAE仅适用于推导的模板参数。在您的情况下,您的方法不依赖于方法调用中的任何参数,因此它不在推论上下文中。实例化类本身时,所有内容都是已知的。

在这种情况下,MSVC完全是错误的。

解决方法:

template<typename... Args>
class C
{
    public:
        template< typename U = std::tuple<Args...>>
            std::enable_if_t< (std::tuple_size<U>::value > 0 ) > foo(const Args&...)
            {
                std::cout << "Args  > 0 type " << std::endl;
            }

        template< typename U = std::tuple<Args...>>
            std::enable_if_t< (std::tuple_size<U>::value == 0)> foo(const Args&...)
            {
                std::cout << "Args 0 type " << std::endl;
            }
};

int main()
{
    C<>{}.foo();
    C<int>{}.foo(1);
}
Run Code Online (Sandbox Code Playgroud)

我不知道为什么需要这样的重载,因为如果参数列表为空,则只需要为此写一个重载而根本没有任何SFINAE东西。

如果您的编译器没有过时(仅c ++ 14),则使用起来会容易得多constexpr if

template <typename... Args>
struct C
{
    void foo (const Args&... args)
    {
        if constexpr ( sizeof...(args) == 0)
        {
            std::cout << "0" << std::endl;
        }
        else
        {
            std::cout << ">0" << std::endl;
        }
    }
};

int main ()
{
    C<>    c0;
    C<int> c1;
    c0.foo();
    c1.foo(42);
}
Run Code Online (Sandbox Code Playgroud)

评论后编辑:

为了避免SFINAE,您还可以使用如下专用的模板类:

// provide common stuff here
template <typename ... ARGS>
class CAll { protected: void DoSomeThing(){ std::cout << "Do some thing" << std::endl; } };

template<typename ... ARGS>
class C;

// special for no args
template<>
class C<>: public CAll<>
{   
    public:
        void foo() 
        {
            std::cout << "none" << std::endl; 
            this->DoSomeThing();
        }   
};  

//special for at minimum one arg
template<typename FIRST, typename ... REST>
class C<FIRST, REST...>: public CAll<FIRST, REST...>
{   
    public:
        void foo( FIRST&, REST&... )
        {   
            std::cout << "lvalue" << std::endl;
            this->DoSomeThing();
        }

        void foo( FIRST&&, REST&&... )
        {   
            std::cout << "rvalue" << std::endl;
            this->DoSomeThing();
        }   
};  

int main()
{   
    int a;
    C<>{}.foo();
    C<int>{}.foo(1);
    C<int>{}.foo(a);
}
Run Code Online (Sandbox Code Playgroud)


max*_*x66 5

正如Klaus更好地解释的那样,您的原始代码不起作用,因为std::enable_if_t需要检查方法本身的模板,而该类的模板列表还不够。

我提出了克劳斯解决方案的简化替代方案。

首先,您需要一个模板参数来检查;您可以使用默认值,该值由类(Args...)的模板参数推导出来。

当然,您可以使用带有的的类型std::tupleArgs...但是考虑到您只对Args...参数的数量感兴趣,我发现使用以std::size_t数量为参数初始化的模板参数更简单Args...

template <std::size_t N = sizeof...(Args)>
std::enable_if_t<N> foo (Args const & ... args)
 { std::cout << "N args" << std::endl; }  
Run Code Online (Sandbox Code Playgroud)

关于零参数版本,无需使其成为模板版本;您可以简单地使用零参数编写它

void foo ()
 { std::cout << "zero args" << std::endl; }
Run Code Online (Sandbox Code Playgroud)

如果为零Args...,则非模板版本的优先级高于模板一。

以下是完整的编译示例

#include <iostream>
#include <type_traits>

template <typename... Args>
struct C
 {
   void foo ()
    { std::cout << "zero args" << std::endl; }

   template <std::size_t N = sizeof...(Args)>
   std::enable_if_t<N> foo(const Args&... args)
    { std::cout << "N args" << std::endl; }
};

int main ()
 {
   C<>    c0;
   C<int> c1;
   c0.foo();
   c1.foo(42);
 }
Run Code Online (Sandbox Code Playgroud)