C++ 模板实例化:直接使用 enable_if,或使用辅助 class

C++ template instantations: using enable_if directly, or with an auxiliary class

这段代码工作正常:

#include <type_traits>

using namespace std;

enum class Type : char { Void };
struct FieldA { static constexpr Type type = Type::Void; };

template<typename Field> struct Signal {};
template<typename Field> struct SignalA : public Signal<Field> {};
struct SignalB : public Signal<void> {};
struct DerivedB : public SignalB {};

template<typename Signal, typename = void> struct Apply;

template<typename Field>
struct Apply<SignalA<Field>, typename std::enable_if<Field::type == Type::Void>::type> {};

template<typename Signal_t>
struct Apply<Signal_t, typename enable_if<is_base_of<SignalB, Signal_t>::value>::type>
{};

int main ()
{ Apply<SignalA<FieldA> > a; }

但我想要的是提高可读性删除长 enable_ifs,所以,让我们攻击,例如,第二个 enable_if 与辅助 class:

template<typename Signal>
struct IsBaseOfB
{ using type = typename enable_if<is_base_of<SignalB, Signal>::type; };

并用它更改第二个 Apply 偏特化:

template<typename Signal_t>
struct Apply<Signal_t, typename IsBaseOfB<Signal_t>::type>
{};

即使唯一可能的专业化是第一个,gcc 也会抛出以下错误:

main.cpp: In instantiation of 'struct IsBaseOfB<SignalA<FieldA> >':
main.cpp:21:78: error: no type named 'type' in 'struct std::enable_if<false, void>'

{ using type = typename enable_if<is_base_of<SignalB, Signal_t>::value>::type; };

这很明显,因为 enable_if 条件不适合 SignalA<FieldA>

我不明白为什么不忽略专业化失败以获得第一个专业化(我知道它有效)。

错误不在模板参数推导的 "the immediate context" 中,因此 SFINAE 不适用(即这是一个错误,而不是替换失败)。

C++ 标准并没有真正定义 "immediate context" 但我试图在 What is exactly the “immediate context” mentioned in the C++11 Standard for which SFINAE applies?

的已接受答案中给出一个简单的解释

简而言之,问题是编译器看到 Apply<SignalA<FieldA>> 并愉快地将模板参数替换为 struct Apply<Signal_t, typename IsBaseOfB<Signal_t>::type>,这没有发生错误,因为 IsBaseOf 确实 有一个嵌套的 type 成员,但是这会触发 IsBaseOf::type 的实例化,这是错误的。错误不在推导的直接上下文中(它在 IsBaseOf 主体的其他地方,它被实例化为副作用)。

另一种看待问题的方式是 IsBaseOf::type 是无条件声明的,而不仅仅是当 is_base_of 特征为真时,所以总是 声明 作为一种类型...但有时它的 定义 格式不正确。推导找到声明并继续超过 SFINAE 应用的点,然后在需要 IsBaseOf::type 的定义时发现致命错误。

您可以使用别名模板(如 Filip 的回答所示)或使用继承来解决它,因此 IsBaseOf::type 仅有条件地存在:

template<typename Signal>
struct IsBaseOfB : enable_if<is_base_of<SignalB, Signal>::value>
{ };

这样,嵌套的 type 成员仅在 enable_if 基础 class 声明它时出现。

简介

SFINAE 仅适用于替换错误发生在模板实例化的 直接上下文 中,如果替换错误发生在所述程序格式错误的模板。

  • What is exactly the “immediate context” mentioned in the C++11 Standard for which SFINAE applies?

解决方案

要么使替换错误发生在 template<typename> struct IsBaseOfB 实例化的直接上下文中,要么简单地定义一个 模板别名声明 ,它将等效于你相当冗长的特点。


模板别名声明(推荐)

template<typename Signal>
using IsBaseOfB = enable_if<is_base_of<SignalB, Signal>::value>;


使用继承

template<typename Signal>
struct IsBaseOfB
  : enable_if<is_base_of<SignalB, Signal>::value>
{ };