13

我正在通过构建自己的向量版本来练习 C++,名为“向量”。我有两个构造函数,填充构造函数和范围构造函数。他们的声明如下所示:

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
    ......
    */
}

fill 构造函数用 num 的 val 填充容器;并且范围构造函数将范围 [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);

一切都很好,调用了填充构造函数。但这显然不直观且用户友好。

那么,有没有一种方法可以直观地使用填充构造函数?

4

4 回答 4

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());
}


上述程序应首先通过检查类型是否为整数类型(因此不是迭代器)来调用填充构造函数。


顺便说一句,在您的范围构造函数的实现中,您应该使用std::distance(first, last)而不是last - first. 在迭代器上显式使用-运算符会将您限制在RandomAccessIterator类型上,但您希望支持InputIterator哪种是最通用的迭代器类型。

于 2012-12-26T03:06:40.107 回答
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啊!:

不知何故,下面的这个函数标识了要调用哪个版本的构造函数。

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);
    }

上面显示的函数有(至少)2 个版本在第三个变量上重载,第三个变量是上述函数_Construct返回的标记。_Iter_cat根据该类别的类型,选择正确的重载_Construct

最终编辑: iterator_traits<_Iter>是一个似乎为许多不同的常见品种模板化的类,每个品种都返回适当的“类别”类型

解决方案:在 MS VC++ 的情况下,第一个参数类型的模板专业化似乎是std库如何处理这种混乱情况(原始值类型)。也许您可以研究一下并效仿?

出现问题(我认为)是因为对于原始值类型,Typesize_t变量是相似的,因此选择了具有两种相同类型的模板版本。

于 2012-12-26T03:12:01.213 回答
3

标准库实现所面临的问题是相同的。有几种方法可以解决它。

  • 您可以为所有整数类型精心提供非模板重载构造函数(代替第一个参数)。

  • 您可以使用基于 SFINAE 的技术(如enable_if)来确保不为整数参数选择范围构造函数。

  • if您可以在检测到整数参数(通过使用)后在运行时(通过使用)分支范围构造函数is_integral,并将控制重定向到正确的构造代码。分支条件将是一个编译时值,这意味着编译器可能会在编译时减少代码。

  • 您可以简单地查看您的标准库实现版本,看​​看他们是如何做到的(尽管从抽象 C++ 语言的角度来看,他们的方法不需要是可移植的和/或有效的)。

于 2012-12-26T03:28:16.237 回答
2

这种模糊性给早期的库实现者带来了问题。这就是所谓的“做正确的事”效应。据我所知,您需要SFINAE来解决它……它可能是该技术的首批应用之一。(一些编译器欺骗并破解了它们的重载解析内部结构,直到在核心语言中找到了解决方案。)

此问题的标准规范是 C++98 和 C++03 之间的主要区别之一。从 C++11,第 23.2.3 节开始:

14 对于本条和第 21 条中定义的每个序列容器:

— 如果构造函数

       template <class InputIterator>
       X(InputIterator first, InputIterator last,
         const allocator_type& alloc = allocator_type())

使用不符合输入迭代器条件的类型 InputIterator 调用,则构造函数不应参与重载决议。

15 实现确定类型不能作为输入迭代器的程度未指定,但至少整数类型不应作为输入迭代器。

于 2012-12-26T03:12:33.013 回答