SFINAE:“ enable_if不能用于禁用此声明”

时间:2018-08-29 12:06:51

标签: c++ c++11 sfinae clang++

为什么在以下情况下不能使用enable_if

我想检测模板对象是否具有成员函数notify_exit

template <typename Queue>
class MyQueue
{
   public:
    auto notify_exit() -> typename std::enable_if<
            has_member_function_notify_exit<Queue, void>::value,
            void
        >::type;

    Queue queue_a;
};

初始化为:

MyQueue<std::queue<int>> queue_a;

我不断得到(c语6):

example.cpp:33:17: error: failed requirement 'has_member_function_notify_exit<queue<int, deque<int, allocator<int> > >, void>::value';
      'enable_if' cannot be used to disable this declaration
            has_member_function_notify_exit<Queue, void>::value,

或(g ++ 5.4)

In instantiation of 'class MyQueue<std::queue<int> >':
33:35:   required from here
22:14: error: no type named 'type' in 'struct std::enable_if<false, void>'

我尝试了很多不同的方法,但无法弄清楚为什么我不能使用enable_if来禁用此功能。这不是enable_if的目的吗?

我放了full example here(和cpp.sh link that often fails

我在SO上发现了类似的Q / A,但通常这些问题更为复杂,并尝试不同的方法。

3 个答案:

答案 0 :(得分:4)

实例化sh index.sh > timezones.json时,模板参数MyQueue<std::queue<int>>被替换为类模板。在导致使用std::queue<int>的成员函数声明中,该函数不存在。那是一个错误。您不能使用不存在的类型来声明函数。

正确使用typename std::enable_if<false, void>::type必须取决于模板参数deduced。在模板自变量推导过程中,如果将推导的模板自变量替换为模板参数失败(即“替代失败”),那么您不会立即得到错误,只会导致推导失败。如果推导失败,则该函数不是重载解决方案的候选者(但仍将考虑其他重载)。

但是在您的情况下,调用函数时不推导出template参数,因为它来自周围的类模板,所以它是已知的。这意味着替换失败 是一个错误,因为该函数的声明是错误的,甚至在尝试执行重载解析来调用它之前。

您可以通过将函数转换为函数模板来修复示例,因此它具有必须推导的模板参数:

enable_if

此处template<typename T = Queue> auto notify_exit() -> typename std::enable_if< has_member_function_notify_exit<T, void>::value, void >::type; 的条件取决于enable_if而不是T,因此直到您尝试替换模板参数之前,都不知道Queue成员是否存在::type。函数模板具有默认的模板参数,因此,如果仅调用T而没有任何模板参数列表,则它等效于notify_exit(),这意味着notify_exit<Queue>()的条件取决于{{1} },就像您最初想要的那样。

此函数可能被滥用,因为调用者可以根据错误的类型将其作为enable_if来欺骗Queue条件。如果调用者这样做,他们应该得到编译错误。

使代码起作用的另一种方法是对整个类模板进行部分专业化处理,以在不需要时简单地删除该函数:

notify_exit<SomeOtherType>()

您可以避免以几种不同的方式重复整个类的定义两次。您可以将所有通用代码提升到基类中,而仅在依赖它的派生类中添加enable_if成员。或者,您可以仅将条件部分移到基类中,例如:

template <typename Queue,
          bool Notifiable
            = has_member_function_notify_exit<Queue, void>::value>
class MyQueue
{
  public:
    void notify_exit();

    Queue queue_a;
};

// partial specialization for queues without a notify_exit member:
template <typename Queue>
class MyQueue<Queue, false>
{
  public:
    Queue queue_a;
};

答案 1 :(得分:3)

实例化模板会导致其包含的所有声明的成员实例化。此时,您提供的声明格式不正确。此外,SFINAE不适用于此处,因为在实例化类模板时我们不会解决重载。

您需要使成员成为带有有效声明的内容,并确保检查延迟到重载解析为止。我们可以通过将notify_exit本身作为模板来完成这两者:

template<typename Q = Queue>
auto notify_exit() -> typename std::enable_if<
        has_member_function_notify_exit<Q, void>::value,
        void
    >::type;

A working cpp.sh example

答案 2 :(得分:3)

使用C ++ 20和概念,您可以使用requires

void notify_exit() requires has_member_function_notify_exit<Queue, void>::value;