C++ SFINAE:“;启用“如果无法使用U禁用此声明”;
为什么我不能在以下上下文中使用C++ SFINAE:“;启用“如果无法使用U禁用此声明”;,c++,c++11,sfinae,clang++,C++,C++11,Sfinae,Clang++,为什么我不能在以下上下文中使用enable\u if 我想检测模板对象是否具有成员函数notify\u exit template <typename Queue> class MyQueue { public: auto notify_exit() -> typename std::enable_if< has_member_function_notify_exit<Queue, void>::value,
enable\u if
我想检测模板对象是否具有成员函数notify\u 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
{
公众:
自动通知_exit()->typename std::启用_if<
具有成员函数通知退出::值,
无效的
>::类型;
队列a;
};
首字母缩写为:
MyQueue<std::queue<int>> queue_a;
MyQueue\u a;
我不断得到(叮当声6):
example.cpp:33:17:错误:失败的需求“has_member_function_notify_exit::value”;
“enable_if”不能用于禁用此声明
具有成员函数通知退出::值,
或(g++5.4):
在“类MyQueue”的实例化中:
33:35:从这里开始需要
22:14:错误:“struct std::enable_if”中没有名为“type”的类型
我尝试了很多不同的方法,但不明白为什么不能使用enable\u if
禁用此功能。这不正是enable\u if
的用途吗
我放了一个(和)
我发现类似的Q/As也是如此,但通常情况下,这些Q/As更复杂,并且尝试了一些不同的东西。实例化模板会导致它包含的所有声明的成员实例化。此时,您提供的声明格式很差。此外,SFINAE在这里不适用,因为在实例化类模板时,我们不会解决重载问题 您需要使用有效的声明将该成员转换为其他成员,并确保检查延迟到重载解析。我们可以通过制作
notify_exit
模板本身来实现这两个目的:
template<typename Q = Queue>
auto notify_exit() -> typename std::enable_if<
has_member_function_notify_exit<Q, void>::value,
void
>::type;
模板
自动通知_exit()->typename std::启用_if<
具有成员函数通知退出::值,
无效的
>::类型;
使用C++20和concept,您可以使用
requires
:
void notify_exit() requires has_member_function_notify_exit<Queue, void>::value;
void notify\u exit()需要有\u成员\u函数\u notify\u exit::value;
实例化MyQueue
时,模板参数std::queue
被替换到类模板中。在成员函数声明中,导致使用不存在的typename std::enable_if::type
。那是个错误。不能使用不存在的类型声明函数
如果,则是否正确使用enable_必须取决于所使用的模板参数。在模板参数推导过程中,如果用推导出的模板参数替换模板参数失败(即“替换失败”),则不会立即出现错误,它只会导致推导失败。如果扣除失败,则该函数不是重载解析的候选函数(但仍将考虑任何其他重载)
但是在您的例子中,调用函数时不会推导模板参数,因为它来自周围的类模板,所以它是已知的。这意味着替换失败是一个错误,因为在您尝试执行重载解析以调用函数之前,函数的声明格式不正确
您可以通过将函数转换为函数模板来修复示例,这样它就有了一个必须推导的模板参数:
template<typename T = Queue>
auto notify_exit() -> typename std::enable_if<
has_member_function_notify_exit<T, void>::value,
void
>::type;
可以通过几种不同的方式避免将整个类定义重复两次。您可以将所有公共代码提升到基类中,并且只在依赖它的派生类中添加notify\u 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();
}
模板
类MyQueueBase
{
公众:
void notify_exit();
};
//不包含notify_exit成员的队列的部分专门化:
模板
类MyQueueBase
{ };
模板
类MyQueue:公共MyQueueBase
{
公众:
//其他同学。。。
队列a;
};
模板
void MyQueueBase::notify_exit()
{
静态_cast(this)->队列_a.notify_exit();
}
您好,我会尽快(在一小时内)尝试一下。但实际上,我不清楚您指出的两个问题。为什么宣言格式不正确?我不明白“当类模板被实例化时,我们没有解决重载问题”,这是什么意思?我想这正是启用\u的目的,如果是为了什么?谢谢你的帮助@马特-斯芬纳不是移除声明的魔法。这是一种机制,允许在重载解析过程中忽略格式错误的模板函数,以支持更合适的重载。SFINAE不适用于仅仅以类成员的身份出现的情况。实例化类时,成员的声明也是如此。但是编译器无法实例化notify_exit
的声明,因为没有返回类型。这完全是不正确的。模板声明是有效的,因为现在启用if不会立即失败。它只在调用站点失败。更准确的说法是,SFINAE在模板参数替换过程中禁用了一些东西(这就是SFINAE中的s所代表的)。该替换是在模板参数推导过程中完成的,该过程在重载解析过程中发生。如果发生替换失败,则演绎失败,并且无法调用该函数。但是在OP的示例中没有模板参数推断,模板参数是显式提供的:MyQueue
。由于您没有推导参数,因此替换失败不仅会导致推导失败,还会导致错误。知道这一点非常棒。现在我还以为我还在使用MSVC 2013的C++11上。MyQueue
的最后一个例子也可以是MyQueueBase
,这样class MyQueue:public MyQueueBase
就可以真正为OP提供他们想要的条件成员,而无需重复两次类定义。我知道我是在一个标准库实现者的回答下写这篇文章的,但我希望您能为了OP的利益添加它。其他人
template<typename T = Queue>
auto notify_exit() -> typename std::enable_if<
has_member_function_notify_exit<T, void>::value,
void
>::type;
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;
};
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();
}