我正在通过构建我自己的vector版本来实践C ++,名为“Vector”。我有两个构造函数,填充构造函数和范围构造函数。他们的声明如下:
template <typename Type>
class Vector {
public:
// fill constructor
Vector(size_t num, const Type& cont);
// range constructor
template<typename InputIterator> Vector(InputIterator first, InputIterator last);
/*
other members
......
*/
}
填充构造函数用num的num填充容器;并且范围构造函数将[first,last]范围内的每个值复制到容器中。它们应该与STL向量的两个构造函数相同。
他们的定义如下:
//fill constructor
template <typename Type>
Vector<Type>::Vector(size_t num, const Type& cont){
content = new Type[num];
for (int i = 0; i < num; i ++)
content[i] = cont;
contentSize = contentCapacity = num;
}
// range constructor
template <typename Type>
template<typename InputIterator>
Vector<Type>::Vector(InputIterator first, InputIterator last){
this->content = new Type[last - first];
int i = 0;
for (InputIterator iitr = first; iitr != last; iitr ++, i ++)
*(content + i) = *iitr;
this->contentSize = this->contentCapacity = i;
}
然而,当我尝试使用它们时,我有区别它们的问题。 例如:
Vector<int> v1(3, 5);
使用这行代码,我打算创建一个包含三个元素的Vector,每个元素都是5.但是编译器会使用范围构造函数,将“3”和“5”都视为“ InputIterator“,毫无意外,会导致错误。
当然,如果我将代码更改为:
Vector<int> v1(size_t(3), 5);
一切都很好,填充构造函数被调用。但这显然不直观且用户友好。
那么,有没有一种方法可以直观地使用填充构造函数?
答案 0 :(得分:12)
您可以使用std::enable_if
(或boost::enable_if
如果您不使用C ++ 11)来消除构造函数的歧义。
#include <iostream>
#include <type_traits>
#include <vector>
using namespace std;
template <typename Type>
class Vector {
public:
// fill constructor
Vector(size_t num, const Type& cont)
{
cout << "Fill constructor" << endl;
}
// range constructor
template<typename InputIterator> Vector(InputIterator first, InputIterator last,
typename std::enable_if<!std::is_integral<InputIterator>::value>::type* = 0)
{
cout << "Range constructor" << endl;
}
};
int main()
{
Vector<int> v1(3, 5);
std::vector<int> v2(3, 5);
Vector<int> v3(v2.begin(), v2.end());
}
上面的程序应首先通过检查类型是否为整数类型来调用fill构造函数(因此不是迭代器。)
顺便说一句,在范围构造函数的实现中,您应该使用std::distance(first, last)
而不是last - first
。在迭代器上明确使用-
运算符会限制您使用RandomAccessIterator
类型,但是您希望支持InputIterator
这是最常用的Iterator类型。
答案 1 :(得分:3)
即使std::vector
似乎也存在此问题。
std::vector<int> v2(2,3);
选择
template<class _Iter>
vector(_Iter _First, _Iter _Last)
在Visual C ++中,尽管它应该更接近非模板化的情况..
编辑:上面的功能(正确)将结构委托给下面的一个。我完全失去了..
template<class _Iter>
void _Construct(_Iter _Count, _Iter _Val, _Int_iterator_tag)
编辑#2 AH!:
不知何故,下面这个函数标识了要调用哪个版本的构造函数。
template<class _Iter> inline
typename iterator_traits<_Iter>::iterator_category
_Iter_cat(const _Iter&)
{ // return category from iterator argument
typename iterator_traits<_Iter>::iterator_category _Cat;
return (_Cat);
}
上面显示的_Construct
函数在第三个变量上有(至少)2个版本重载,这是上述_Iter_cat
函数返回的标记。根据此类别的类型,选择_Construct
的正确重载。
最终编辑:
iterator_traits<_Iter>
是一个似乎被许多不同的常见变种模仿的类,每个变量都返回适当的“类别”类型
解决方案:在MS VC ++的情况下,第一个争论类型的模板特化是std
库处理这种混乱情况(原始值类型)的方式。也许你可以调查并效仿呢?
问题出现了(我认为),因为对于原始值类型,Type
和size_t
变量是相似的,因此选择具有两个相同类型的模板版本。
答案 2 :(得分:3)
标准库实现面临的问题是相同的。有几种方法可以解决它。
您可以为所有整数类型(代替第一个参数)精心提供非模板重载构造函数。
您可以使用基于SFINAE的技术(如enable_if
)来确保未为整数参数选择范围构造函数。
您可以在检测到整数参数(通过使用if
)并将控制重定向到正确的构造代码后,在运行时(通过使用is_integral
)对范围构造函数进行分支。分支条件是编译时的值,这意味着编译器可能会在编译时减少代码。
您可以直接查看您的标准库实现版本并了解它们是如何做到的(尽管从抽象C ++语言的角度来看,它们的方法不需要是可移植的和/或有效的。)
答案 3 :(得分:2)
这种模糊性给早期的图书馆实施者带来了问题。它被称为“做正确的事”效果。据我所知,你需要SFINAE来解决它...它可能是该技术的第一个应用之一。 (有些编译器欺骗并破解了他们的重载决策内部,直到在核心语言中找到解决方案。)
此问题的标准规范是C ++ 98和C ++ 03之间的主要区别之一。从C ++ 11,§23.2.3:
14对于本条款和第21条中定义的每个序列容器:
- 如果构造函数
使用不符合输入迭代器条件的InputIterator类型调用template <class InputIterator> X(InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type())
,然后构造函数不应参与重载解析。
15实现确定某个类型不能是输入迭代器的程度是未指定的,除非作为最小整数类型不符合输入迭代器的条件。