Mar*_*wen 6 c++ templates c++11
许多容器类模板都有一个构造函数,它接受一个计数和一个样本元素作为参数,另一个构造函数接受一对输入迭代器。其他方法如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
}
Run Code Online (Sandbox Code Playgroud)
这是一个活生生的例子。类模板input_iter
试图做一些多余的事情:检查类型是否不是整数,然后它实际上是一个输入迭代器。最好我只使用第二部分,但这不起作用;努力当我得到一个模板实例错误int
的I
(无iterator_category
),显然这不是SFINAE。虽然我不明白为什么会这样,但我添加了第一部分以避免错误,使用and
( &&
) 运算符的惰性,但显然无济于事。我实际上可以通过删除条件的第二部分来编译它,所以我真正的问题不是让它以某种方式工作,而是了解发生了什么。
我注意到的一件奇怪的事情是,g++
只给出了一条错误消息,提到了a3
. 因此,一方面,使无符号显然避免试图迭代器过载(即使其他参数可以很容易地转换为unsigned),而在另一方面重复违规定义一个参数a3
的a4
和a5
不重复的错误信息(但如果一个修复a3
定义,肯定gcc
对a4
定义犹豫不决)。另请注意,虽然修复所有变量使其保持沉默,但clang++
并未指向其中一个a
变量的任何特定定义。
我做错了什么,和/或显然应该做不同的事情?
在评论和更多实验的帮助下,我将尝试自己制定答案。
我认为替换失败只有在相关声明的“直接上下文”中发生时才是 (SFI)NAE,这显然是 14.8.2;8 中使用的一个神圣的(但没有非常清楚地解释)术语,其中表示“仅函数类型及其模板参数类型的直接上下文中的无效类型和表达式可能会导致推导失败”。撇开构造函数方法实际上没有任何类型不谈,当尝试为(否则未使用的)第二个模板参数构造默认类型,但将派生整数类型替换为InputIt
fragment时,示例中的失败/错误就会出现typename std::enable_if...::type
。查找布尔(非类型)模板参数的值std::enable_if
本身是在(类型替换的)“直接上下文”中执行的;上一段 14.8.2;7 对此很清楚(“表达式不仅包括常量表达式,例如出现在数组边界中或作为非类型模板参数的常量表达式......”)。然而,计算该布尔值需要形成类模板专门化input_iter<InputIt>
,这是不再在直接上下文中执行的辅助活动。事实证明,在模板特化期间执行了另一个模板特化,即
std::iterator_traits<InputIt>
;这也不是在直接上下文中,但这个事实在这里无关紧要,因为后一个专业化永远不会失败。然而,当推断为整型时,所得结构没有适当定义的iterator_category
成员,这会导致在这种情况下专业化失败。由于不在原始类型替换的直接上下文中,这种失败使程序格式错误。InputIt
input_iter<InputIt>
因此,真正的罪魁祸首是将失败与 SFINEA 应用的上下文分开的元素是专业化input_iter<InputIt>
,而std::iterator_traits<InputIt>
只是间接涉及。因此,可以通过删除模板执行的类型推导input_iter
并将布尔表达式直接输入到enable_if
. 由于我想避免使用模板别名,因此这需要(因为不存在布尔值模板之类的东西)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);
Run Code Online (Sandbox Code Playgroud)
该std::is_integral
部分并不是真正有用,可以省略,只留下该std::is_base_of
部分。生成的代码可以正确编译并运行,事实上,即使使用 gcc 4.6 也是如此。由此可见,无论外表如何,都可以与 SFINAE 一起使用std::iterator_traits
。当模板别名可用时,其中之一可用于将部分enable_if<...>::type
从构造函数声明中取出,使其看起来更令人胃口大开,但如果没有它们,我将无法看到如何做到这一点,并且仍然可以正确调用 SFINAE。