如何区分C ++ 11中的填充构造函数和范围构造函数?

时间:2017-08-23 19:19:10

标签: c++ c++11 constructor overloading

我怀疑这个webpage中给出的std::vector(和许多其他STL类型)的填充构造函数和范围构造函数的原型是不正确的,所以我实现了NaiveVector来模仿这些两个原型。

我的代码是:

#include <iostream>
#include <vector>
using namespace std;

template <typename T>
struct NaiveVector {
  vector<T> v;
  NaiveVector(size_t num, const T &val) : v(num, val) { // fill
    cout << "(int num, const T &val)" << endl;
  }

  template <typename InputIterator>
  NaiveVector(InputIterator first, InputIterator last) : v(first, last) { // range
    cout << "(InputIterator first, InputIterator last)" << endl;
  }

  size_t size() const { return v.size(); }
};

int main() {
  NaiveVector<int> myVec1(5,1);                   // A
  cout << "size = " << myVec1.size() << endl;
  for (auto n : myVec1.v) { cout << n << " "; }
  cout << endl;

  cout << "-----" << endl;

  vector<int> vec({1,2,3,4,5});               
  NaiveVector<int> myVec2(vec.begin(), vec.end());// B
  cout << "size = " << myVec2.size() << endl;
  for (auto n : myVec2.v) { cout << n << " "; }
  cout << endl;
}

输出是:

$ g++ overload.cc -o overload -std=c++11
$ ./overload
(InputIterator first, InputIterator last) // should be: (int num, const T &val)
size = 5
1 1 1 1 1
-----
(InputIterator first, InputIterator last)
size = 5
1 2 3 4 5

正如我从一开始就怀疑的那样,编译器无法正确区分这两个构造函数。然后我的问题是:std::vector填充构造函数和范围构造函数如何相互区分?

改写:如何实现此NaiveVector的两个构造函数?

  

这个问题似乎与this问题重复,但答案并不令人满意。此外,C ++ 11本身并不提供is_iterator<> ..(MSVC有很多黑客攻击)。

编辑:允许将右值绑定到常量左值引用,因此NaiveVector的第一个构造函数对A有效。

3 个答案:

答案 0 :(得分:5)

<强> C ++ 03

[lib.sequence.reqmts] / 9

  

对于本节和第21节中定义的每个序列:

     
      
  • 构造函数

    template <class InputIterator>
    X(InputIterator f, InputIterator l, const Allocator& a = Allocator())
    
         

    具有与以下相同的效果:

    X(static_cast<typename X::size_type>(f),
      static_cast<typename X::value_type>(l),
      a)
    
         

    如果InputIterator是整数类型。

  •   
     

...

[lib.sequence.reqmts] / 11

  

序列实现者可以满足此要求的一种方法是专门化成员模板   每个整体类型。也存在不太繁琐的实现技术。

换句话说,标准说如果范围构造函数是通过重载决策选择的,那么&#34; iterator&#34; type实际上是一个整数类型,它必须通过强制转换它的参数类型来委托给fill构造函数,以强制后者完全匹配。

<强> C ++ 11 / C ++ 14

[sequence.reqmts] / 14

  

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

     
      
  • 如果构造函数

    template <class InputIterator>
    X(InputIterator first, InputIterator last,
      const allocator_type& alloc = allocator_type())
    
         使用类型InputIterator调用

    不符合输入迭代器的条件,然后构造函数不应参与重载解析。   ...

  •   

[sequence.reqmts] / 15

  

实现确定某个类型不能是输入迭代器的程度未指定,   除了作为最小积分类型不符合输入迭代器。

这或多或少是标准提示您使用SFINAE的方式(它在C ++ 11中可靠地工作而不是C ++ 03)。

<强> C ++ 17

措辞类似,只是关于不是迭代器的整数类型的段落已移至[container.requirements.general]/17

<强>结论

您可以编写范围构造函数,如下所示:

template <typename InputIterator,
          typename = std::enable_if<is_likely_iterator<InputIterator>>::type>
NaiveVector(InputIterator first, InputIterator last)

is_iterator帮助器模板可能只是取消对整数类型的限定并接受所有其他类型,或者它可能会执行更复杂的操作,例如检查是否存在指示类型为输入的std::iterator_traits特化迭代器(或更严格)。请参阅libstdc++ sourcelibc++ source

这两种方法都与C ++ 11及更高版本中std::vector的标准强制行为一致。建议使用后一种方法(并且将与真正的std::vector在典型实现上可能做的一致),因为如果您传递的参数可以隐式转换为fill构造函数所期望的类型,那么您希望班级会做正确的事情&#34;而不是只选择范围构造函数使其无法编译。 (虽然我怀疑这是相当不常见的。)相对于C ++ 03 std::vector,它是一个符合标准的扩展&#34;因为在这种情况下构造函数调用不会编译。

答案 1 :(得分:2)

如果您仔细阅读http://en.cppreference.com/w/cpp/container/vector/vector处的文档,您会注意到以下内容(强调我的):

  

4)使用范围[first, last)

的内容构造容器      

如果vector(static_cast<size_type>(first), static_cast<value_type>(last), a)是整数类型,则此构造函数与InputIt具有相同的效果。 (直到C ++ 11)

     

如果InputIt满足InputIterator,则此重载仅参与重载解析,以避免出现过载歧义(2)

混淆不是std::vector,而是你对你的哪些构造函数被调用的期望。您的代码没有进行检查以确保仅在满足std::vector的上述条件时才调用第二个构造函数。

在您的情况下,当您使用

NaiveVector<int> myVec1(5,1);

可以使用int作为模板参数来调用第二个构造函数,而无需任何转换。编译器需要将int转换为size_t才能调用第一个构造函数。因此,第二个构造函数更适合。

答案 2 :(得分:1)

您可以尝试添加一些类型特征检查来验证第二个构造函数的参数是否是T元素上的迭代器:

template
<
    typename InputIterator
,   typename = typename ::std::enable_if
    <
        ::std::is_same
        <
            T &
        ,   typename ::std::remove_const
            <
                decltype(*(::std::declval< InputIterator >()))
            >::type
        >::value
    ,  void
    >::type
>
NaiveVector(InputIterator first, InputIterator last) : v(first, last)
{
    cout << "(InputIterator first, InputIterator last)" << endl;
}

Run this code online