在参数列表中使用 std::enable_if

Using std::enable_if in a parameters list

在 C++ 中,请考虑以下示例:

template <typename first, typename... params> struct q;

template <typename first> struct q <first>
{
    q()
    {
        cout<<"x"<<endl;
    }
};

template <typename first, typename... params> struct q
{
    q()
    {
        cout<<"x";
        q <params...> ();
    }
};

int main()
{
    q <int, int> ();
    q <int, int, enable_if<true, bool>::type> ();
    // q <int, int, enable_if<false, bool>::type> ();
}

我定义了一个接受任意数量参数的模板结构。然后我用一组参数实例化它。每个构造函数都将构建一个新的 q,除了第一个之外,它的所有参数都包含在内。只有一个参数的 q 将不再调用任何递归。

每次调用一个q的构造函数,都会打印出一个"x"。因此,main 的第一行将使程序打印出 "xx",而第二行将打印出 "xxx",因为 enable_if 实际上已启用。现在,如果第三行会导致我的程序打印出 "xx" (即:构建一个 q ),我将不胜感激。可悲的是,我得到的是错误

No template named 'type' in std::enable_if <false, bool>

我该怎么做才能更正我的示例并让它发挥作用?

谢谢

enable_if 用于可以使用 SFINAE 的上下文,这意味着在模板内部。

在非模板上下文(或不依赖于模板参数的上下文)中,当条件为假时,引用 enable_if<false, T>::type 是错误的,因为该成员不存在。

What could I do to correct my example and make it work?

我不明白你想做什么。您期望 enable_if<false, bool>::type 是什么?没有这个类型,你觉得怎么用?

你误解了std::enable_if的用法。 (我们中的许多人开始了)。 这导致您问了一个技术上被误导的问题,而不是更多 您想要回答的一般问题。您要回答的问题是 我的模板如何使用或 忽略模板参数,仅基于编译时条件?,它不是 一个坏的。

您的印象是 std::enable_if<Cond,T>::type 是一个模板表达式 当 Condtrue 时将实例化为 T,当 nothing 时实例化为 nothing Cond 为假。

Condtrue 时,这是正确的,因为 std::enable_if<true,T> 的定义如下:

std::enable_if<true,T> {
    typedef T type;
};

但是 std::enable_if<false,T> 的定义如下:

std::enable_if<false,T> {};

查看那些定义here

因此std::enable_if<false,T>::type不会消失;对于任何 T, 它成为对成员类型 type 的错误格式的、不可编译的引用 std::enable_if<false,T>,不存在。它成为一个编译器错误。

因此std::enable_if可用于实例化模板class或 根据某些函数的编译时值,函数可能或不可能 布尔常量。并且由于编译器将不考虑任何 如果有可行的替代方案,模板的不可行实例化, 因此,它可用于在备选方案之间进行编译时选择 基于这种条件的模板 class 或函数的实现,例如

template<bool Cond>
typename std::enable_if<Cond,int>::type foo(int i) // A
{
    return i*i;
}

template<bool Cond>
typename std::enable_if<!Cond,int>::type foo(int i) // B
{
    return i + i;
}

在这里,如果 some_bool_constanttruefoo<some_bool_constant>(i) 将产生 i 平方 并且 如果some_bool_constantfalse,我加倍,因为在第一种情况下 B 重载的实例化格式错误,在第二种情况下 是 A 重载变得格式错误。

替代重载让编译器可以选择要考虑的实例化 每次它看到 foo<some_bool_constant>(x),其中一个结果是错误的,另一个是可行的, 取决于 some_bool_constant 的真值。它会选择在每种情况下都可行的一个。这个 行为称为 SFINAE,并且 是你需要了解的才能理解std::enable_if.

但是您的没有替代实例:

q <int, int, enable_if<false, bool>::type> ()

"depending on the value of false"。 false 无条件地意味着 false 所以这是错误的 代码。

并考虑:即使 std::enable_if<false,bool>::type 确实 以某种方式消失了,那也会留下:

q <int, int, > ()

仍然格式错误。

What could I do to correct my example and make it work?

其实并不需要太多,而且 std::enable_if 没有特色。让我们解决真正的问题:我的模板如何使用或 忽略模板参数,仅基于编译时条件?

您需要以这种方式定义您的模板 q<First, ... Rest> 和其他一些模板 accept_if<bool Cond, typename T> 对于 q:

的任何实例化
/*(Iq)*/    q<A0,....,Aj,....,Ak>  // 0 <= j <= k

其中 Aj = accept_if<some_bool_constant,t>::type,则如果 some_bool_constant == true(Iq) 将具有与以下完全相同的效果:

q<A0,....,t,....,Ak>

如果 some_bool_constant == false 那么 (Iq) 将具有与以下相同的效果:

q<A0,....,Ak>

而不诉诸 some_bool_constant 的任何运行时测试。 (在 (Iq), '....' 只是可能参数的示意性占位符:无事可做 使用 C++ 包或省略号。)

但是 accept_if<some_bool_constant,t>::type 必须在编译时解析为某些 类型说明符 或者代码格式错误。它不能解决任何问题。所以在 false 的情况下,它必须解析为一个类型 (Iq) 的实例化将作为指定 无数据类型 .

处理的说明符

现在拥有指定无数据类型的类型说明符的引人注目的实用程序 在 C/C++ 中早在很久以前就被认为是 C 的第一个 ANSI 标准。 类型说明符是 void.

从您的评论中我们了解到 q 的模板参数 表示构造函数的实例化应提取的数据类型序列 来自参数传递的来源。在这种情况下,void 非常符合要求 作为类型说明符,表示无事可做。你不能声明一个 void 对象, 更不用说从某个地方提取一个了。

因此我建议您定义accept_if

template<bool Cond, typename T>
using accept_if = std::conditional<Cond,T,void>::type;

accept_if<true,T> = Taccept_if<false,T> = void。阅读 std::conditional

有了这个定义,您只需添加到您现有的专业化 q 还有一个:

template<> struct q<void> {};

q<void> 一个什么都不做的默认构造函数。这是一个示例程序 像你自己的一样,但格式正确,说明了这个解决方案:

#include <type_traits>
#include <iostream>

template<bool Cond, typename T>
using accept_if = typename std::conditional<Cond,T,void>::type;

template <typename first, typename... params> struct q;

template<> struct q<void> {};

template <typename first> struct q <first>
{
    q()
    {
        std::cout << "x" << std::endl;
    }
};

template <typename first, typename... params> struct q
{
    q()
    {
        std::cout << "x";
        q<params...>();
    }
};

int main()
{
    q<int, int>();
    q<int, int, accept_if<true,bool>>();
    q<int, int, accept_if<false,bool>>();
    return 0;
}

输出就是你想要的:

xx
xxx
xx

在不同的情况下,您甚至可能想要 Aj = void 不会(Iq) 中被忽略。但是因为我们可以随意创建类型,你可以 始终创建一个与您的模板完全不同的类型 一定不能忽略,说:struct ignore {}; 然后你会定义:

template<bool Cond, typename T>
using accept_if = std::conditional<Cond,T,ignore>::type;

并专攻:

template<> struct q<ignore> {};