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

Mat*_*att 11 c++ sfinae c++11 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;
};
Run Code Online (Sandbox Code Playgroud)

初始化为:

MyQueue<std::queue<int>> queue_a;
Run Code Online (Sandbox Code Playgroud)

我不断收到(clang 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,
Run Code Online (Sandbox Code Playgroud)

或(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>'
Run Code Online (Sandbox Code Playgroud)

我尝试了很多不同的东西,但不知道为什么我不能enable_if用来禁用这个功能。这不正是enable_if为了什么吗?

我在这里放了一个完整的例子(以及经常失败的 cpp.sh 链接

我在 SO 上发现了类似的 Q/As,但通常那些更复杂并尝试不同的东西。

Jon*_*ely 13

当您实例化MyQueue<std::queue<int>>模板参数时,它std::queue<int>会被替换到类模板中。在导致typename std::enable_if<false, void>::type不存在使用的成员函数声明中。那是一个错误。您不能使用不存在的类型声明函数。

enable_ifmust 的正确使用取决于推导出的模板参数。在模板参数推导期间,如果将推导的模板参数替换为模板参数失败(即“替换失败”),那么您不会立即得到错误,它只会导致推导失败。如果推导失败,则该函数不是重载决议的候选对象(但仍会考虑任何其他重载)。

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

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

template<typename T = Queue>
  auto notify_exit() -> typename std::enable_if<
              has_member_function_notify_exit<T, void>::value,
              void
          >::type;
Run Code Online (Sandbox Code Playgroud)

这里的enable_if条件取决于T而不是Queue,因此在::type您尝试将模板参数替换为 之前,不知道该成员是否存在T。函数模板有一个默认的模板参数,所以如果你只是在notify_exit()没有任何模板参数列表的情况下调用,它等价于notify_exit<Queue>(),这意味着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;
};
Run Code Online (Sandbox Code Playgroud)

您可以通过几种不同的方式避免将整个类定义重复两次。您可以将所有公共代码提升到一个基类中,并且只notify_exit()在依赖它的派生类中添加成员。或者,您可以仅将条件部分移动到基类中,例如:

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

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

template<typename Queue>
class MyQueue : public MyQueueBase<Queue>
{
public:
  // rest of the class ...

  Queue queue_a;
};

template<typename Queue, bool Notifiable>
void MyQueueBase<Queue, Notifiable>::notify_exit()
{
  static_cast<MyQueue<Queue>*>(this)->queue_a.notify_exit();
}
Run Code Online (Sandbox Code Playgroud)

  • `MyQueue` 的最后一个例子也可以是 `MyQueueBase`,这样 `class MyQueue : public MyQueueBase&lt;Queue&gt;` 就可以真正为 OP 提供他们想要的条件成员,而无需重复两次类定义。我知道我是在标准库实现者的回答下写这篇文章的,但我希望你会为了 OP 的利益而添加它。否则,很棒的答案。 (2认同)

Jar*_*d42 7

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

void notify_exit() requires has_member_function_notify_exit<Queue, void>::value;
Run Code Online (Sandbox Code Playgroud)