【问题标题】:Why 'enable_if' cannot be used to disable this declaration here为什么 'enable_if' 不能用于在此处禁用此声明
【发布时间】:2018-11-15 17:50:37
【问题描述】:
#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&amp;&amp;... args),但相同的代码甚至无法在两个 gcc 7.3 中编译> 和 clang 6.0.0 有错误:

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

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

【问题讨论】:

  • 您熟悉 SFINAE 吗?现有的答案本身还不错,但它并不能解释 SFINAE。但是我认为,如果您了解 SFINAE,则该问题已经得到解答。
  • @MSalters:我对 SFINAE 很熟悉,显然不是很熟悉 :) 但幸运的是,足以理解这里给出的答案并进一步探索。

标签: c++ c++14 variadic-templates typetraits enable-if


【解决方案1】:

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&amp;&amp;... args) 将使用右值引用,而第二个 void foo(const Args&amp;... args) 将使用其余函数。它们唯一发生冲突的情况是 C 在没有参数的情况下被实例化。基本上,我想用类模板参数实现完美转发。
  • 非常感谢。
  • “SFINAE 仅适用于推导的模板参数” - 这是真的吗? Afaik SFINAE 也适用于显式指定的模板参数以及默认模板参数,它们都不是作为推导的模板参数获得的。相反,这也适用于 OP 的情况,enable_if 构造需要包含来自应用的实体的 some 模板参数,否则 enable_if 构造本身将永远不会成为模板参数的一部分该实体的替代(其中模板参数推导是一部分),在这种情况下,SFINAE 失败。
  • 确实,SFINAE 确实强调替代 (SFINAE) 而不是演绎(DFINAE?)。
【解决方案2】:

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

我提出了 Klaus 解决方案的简化替代方案。

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

当然,您可以使用采用 std::tuple 或 Args... 的类型,但考虑到您只对 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; }  

关于零args版本,不需要做成模板版本;你可以简单地用零参数编写它

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-16
    • 2022-12-18
    • 1970-01-01
    相关资源
    最近更新 更多