gpt4 book ai didi

c++ - 如何使用 SFINAE 限制输入迭代器的重载

转载 作者:塔克拉玛干 更新时间:2023-11-03 01:44:48 24 4
gpt4 key购买 nike

许多容器类模板有一个以计数和样本元素作为参数的构造函数,以及另一个以一对输入迭代器为参数的构造函数。 insert 等其他方法表现出相同的模式。当模板用整数类型实例化时,天真的实现会遇到麻烦,并且用一对整数调用构造函数(或其他方法):它们的类型相等,输入迭代器方法将给出有效的参数类型推导,但随后无法编译。

我正在寻找一种优雅的方式来指定输入迭代器方法仅参与实际有效输入迭代器类型的(相等)参数类型的重载,或者至少不参与整数类型。我的印象是 SFINAE 是要走的路(但很高兴被告知不同),但是在阅读它时,坦率地说我不太了解规则,而且示例中提供的解决方案很难算得上优雅.

作为附带限制,我希望我的代码能够与具有不完整 C++11 支持的 gcc 4.6 兼容。值得注意的是,我想避免使用模板别名。这是我笨拙的尝试(简单的骨头):

#include <iterator>
#include <type_traits>
#include <vector>

template <typename I>
struct input_iter : public std::integral_constant<bool,
not std::is_integral<I>::value
and std::is_base_of
<std::input_iterator_tag
,typename std::iterator_traits<I>::iterator_category
>::value >
{};

template<typename T>
struct container
{
container (size_t count, const T& init);
template <typename InputIt,
typename = typename std::enable_if<input_iter<InputIt>::value>::type >
container (InputIt first,InputIt last);
};

typedef container<int> int_cont;

void f()
{ std::vector<int> v (5,3);
int_cont a1 (3u,6); // first constructor
int_cont a2 (3u,6); // first constructor
int_cont a3 (3,6); // first constructor
int_cont a4 (3,6); // first constructor
int_cont a5 (3,6); // first constructor
int_cont b(v.begin(),v.end()); // second constructor
}

这是一个 live example .类模板 input_iter 试图做一些冗余的事情:检查类型是否不是整数,然后它实际上是一个输入迭代器。最好我只使用第二部分,但这不起作用;在为 I(没有 iterator_category)尝试 int 时出现模板实例化错误,显然这不是 SFINAE。虽然我不明白为什么会这样,但我添加了第一部分以避免错误,使用 and (&&) 运算符的惰性,但显然徒劳无功。我实际上可以通过删除条件的第二部分来编译它,所以我真正的问题不是让它以某种方式工作,而是了解发生了什么。

我注意到一件奇怪的事情是 g++ 只给出了一条错误消息,提到了 a3 的定义。因此,一方面,使一个参数无符号显然避免了尝试迭代器重载(即使另一个参数可以很容易地转换为无符号),另一方面重复 a3 的违规定义 a4a5 不会重复错误消息(但如果有人修复 a3 定义,gcc 肯定会在 a4 定义)。另请注意,clang++ 并未指向 a 变量之一的任何特定定义,尽管修复所有这些变量会使它保持沉默。

What am I doing wrong, and/or should obviously be doing differently?

最佳答案

在评论和更多实验的帮助下,我将尝试自己制定一个答案。

我收集到,如果替换失败发生在相关声明的“直接上下文”中,则替换失败只是 (SFI)NAE,这显然是 14.8.2;8 中使用的一个神圣的(但解释得不是很清楚)术语说“只有函数类型及其模板参数类型的直接上下文中的无效类型和表达式才能导致推导失败”。抛开构造方法实际上没有任何类型,示例中的失败/错误出现在尝试为(否则未使用的)第二个模板参数构造默认类型,但用派生整数类型代替 InputIt 时。进入片段typename std::enable_if...::type .查找 std::enable_if 的 bool (非类型)模板参数的值本身是在“直接上下文”(类型替换)中执行的;前一段 14.8.2;7 对此很清楚(“表达式不仅包括常量表达式,例如出现在数组边界或作为非类型模板参数的表达式......”)。然而,计算该 bool 值需要形成类模板特化 input_iter<InputIt> ,这是一种辅助事件,不再在直接上下文中执行。事实证明,在该模板特化期间,执行了另一个模板特化,即 std::iterator_traits<InputIt> ;这也不在直接上下文中,但是这个事实在这里无关紧要,因为后者的特化永远不会失败。然而,生成的结构没有适当定义的 iterator_category成员(member)时InputIt被推导为整数类型,这导致特化 input_iter<InputIt>在那种情况下失败。不在原始类型替换的直接上下文中,此失败使程序格式错误。

所以真正的罪魁祸首,将失败与 SFINEA 应用的上下文分开的元素,是特化 input_iter<InputIt> ,而 std::iterator_traits<InputIt>只是间接参与。因此,可以通过删除由 input_iter 执行的类型派生来编译该示例。模板,并将 bool 表达式直接输入 enable_if .因为我想避免使用模板别名,所以这需要(因为没有 bool 值模板这样的东西)忘记 input_iter完全并将其包含的整个表达式移动到构造函数模板声明中,给出

template <typename InputIt,
typename = typename std::enable_if<not std::is_integral<InputIt>::value
and std::is_base_of
<std::input_iterator_tag
,typename std::iterator_traits<InputIt>::iterator_category
>::value>::type >
container (InputIt first,InputIt last);

std::is_integral部分不是很有用,可以省略,只留下 std::is_base_of部分。结果代码 compiles and runs correctly ,事实上即使是 gcc 4.6。这表明不管外表如何,一个人可以使用std::iterator_traits与 SFINAE。当模板别名可用时,其中一个可用于提升 enable_if<...>::type从构造函数声明中分离出来以使其看起来更有食欲,但没有它们我看不出如何做到这一点并仍然正确调用 SFINAE。

关于c++ - 如何使用 SFINAE 限制输入迭代器的重载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25668966/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com