为什么“enable_if”不能用于此处禁用声明

5
#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;
}

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

error: no type named 'type' in 'std::enable_if'; 'enable_if' cannot be used to disable this declaration

我想了解上述代码有什么问题,以及如何修复它?

你熟悉SFINAE吗?现有的答案本身并不差,但它并没有解释SFINAE。然而,我认为如果你理解了SFINAE,这个问题已经被回答了。 - MSalters
1
@MSalters:我对SFINAE很熟悉,显然不是非常熟练 :) 但幸运的是,足以理解这里给出的答案并进一步探索。 - Sajal
2个回答

8

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

我不知道为什么你需要这样的重载,因为如果参数列表为空,你只需编写一个没有任何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);
}

评论后编辑:

为避免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);
}

为什么我需要这样的重载?:如果您查看上面的代码,在两个函数之间,第一个void foo(Args&&... args)将使用rvalue引用,而第二个void foo(const Args&... args)将使用其他参数。它们唯一冲突的时候是当C没有参数实例化时。基本上,我想要实现类模板参数的完美转发。 - Sajal
非常感谢您。 - Sajal
“SFINAE 仅适用于推断的模板参数” - 这是真的吗?就我所知,SFINAE 也可应用于显式指定的模板参数以及默认模板参数,其中没有一个被获得为推断的模板参数。相反,正如在 OP 的情况下一样,enable_if 结构需要包含来自其应用的实体的 某些 模板参数,否则 enable_if 结构本身将永远不会参与该实体的模板参数_代换_(其中模板参数推导是其一部分),在这种情况下 SFINAE 失败。 - dfrib
实际上,SFINAE 确实强调 _替换_(SFINAE),而不是推断(DFINAE?)。 - dfrib

6
正如Klaus所解释的那样,您原始的代码无法工作,因为std::enable_if_t需要检查方法本身的模板,而不仅仅是类的模板列表。
我提出了Klaus方案的简化替代方案。
首先,您需要一个要检查的模板参数;您可以使用一个带有从类的模板参数(Args...)推导出的默认值的模板参数。
当然,您可以使用一个类型,它接受Args...的std::tuple,但考虑到你只对Args...参数的数量感兴趣,我发现使用一个初始化为Args...数量的size_t模板参数会更简单。
template <std::size_t N = sizeof...(Args)>
std::enable_if_t<N> foo (Args const & ... args)
 { std::cout << "N args" << std::endl; }  

关于零参数版本,不需要将其变成模板版本;您可以直接用零个参数编写它。

void foo ()
 { std::cout << "zero args" << std::endl; }

如果没有任何参数,非模板版本将优先于模板版本。

以下是一个完整的编译示例:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接