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