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

Lee*_*hai 1 c++ constructor overloading c++11

我怀疑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;
}
Run Code Online (Sandbox Code Playgroud)

输出是:

$ 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
Run Code Online (Sandbox Code Playgroud)

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

换言:如何实现这两个构造函数NaiveVector

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

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

Bri*_*ian 5

C++ 03

[lib.sequence.reqmts]/9

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

...

[lib.sequence.reqmts]/11

序列实现者可以满足此要求的一种方法是为每个整数类型专门化成员模板.还存在不太麻烦的实现技术.

换句话说,标准说如果范围构造函数通过重载决策被选中但"迭代器"类型实际上是一个整数类型,它必须通过强制转换它的参数类型来委托填充构造函数来强制后者是精确的比赛.

C++ 11/C++ 14

[sequence.reqmts]/14

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

  • 如果是构造函数

    template <class InputIterator>
    X(InputIterator first, InputIterator last,
      const allocator_type& alloc = allocator_type())
    
    Run Code Online (Sandbox Code Playgroud)

    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)
Run Code Online (Sandbox Code Playgroud)

is_iterator助手模板可能只是资格整数类型并接受所有其他类型的,或者它可能会做一些更复杂的如检查是否有std::iterator_traits专业化,指示该类型是输入迭代器(或更严格).请参阅libstdc ++ source,libc ++ source

这两种方法都符合std::vectorC++ 11及更高版本中标准的强制行为.建议使用后一种方法(并且将与实际std::vector可能在典型实现上执行的操作一致),因为如果您传递可隐式转换为fill构造函数所期望的类型的参数,您希望该类将"执行"正确的事情"并没有选择范围构造函数只是让它无法编译.(虽然我怀疑这是相当罕见的.)相对于C++ 03 std::vector,这是一个"符合延伸",因为在这种情况下,构造函数调用不会编译.