猪:"enable_if cannot be used to disable this declaration"

SFINAE: "enable_if cannot be used to disable this declaration"

为什么我不能在以下上下文中使用 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;

我不断收到 (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,

或 (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 (and cpp.sh link that often fails)

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

实例化模板会导致其包含的所有声明的成员实例化。您提供的声明此时只是格式错误。此外,SFINAE 不适用于此处,因为在实例化 class 模板时我们没有解析重载。

您需要使成员具有有效的声明,并确保检查延迟到重载解决。我们可以通过使 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

有了 C++20 和概念,你可以使用 requires:

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

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

enable_if 的正确使用必须取决于 deduced 的模板参数。在模板参数推导期间,如果用推导的模板参数替换模板参数失败(即 "substitution failure"),那么您不会立即收到错误,它只会导致推导失败。如果推导失败,则该函数不是重载决策的候选者(但仍会考虑任何其他重载)。

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

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

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>(),这意味着 enable_if 条件取决于 Queue,如你所愿。

这个函数可能会被滥用,因为调用者可以调用它作为 notify_exit<SomeOtherType>() 来根据错误的类型欺骗 enable_if 条件。如果调用者这样做,他们应该得到编译错误。

使代码工作的另一种方法是对整个 class 模板进行部分专门化,以便在不需要时简单地删除函数:

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

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

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