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

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

本文关键字:to used disable this declaration be cannot enable if SFINAE      更新时间:2023-10-16

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

我不断得到(叮当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的目的吗?

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

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

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

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>(),这意味着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;
};

您可以避免以几种不同的方式重复整个类定义两次。您可以将所有公共代码提升到基类中,并且只在依赖于它的派生类中添加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();
}

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

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

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

您需要将成员变成具有有效声明的内容,并确保检查延迟到重载解决。我们可以通过notify_exit本身成为模板来做到这一点:

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

工作 cpp.sh 示例