vukung
vukung

Reputation: 1884

How does the compiler differentiate between the constructors of std::vector?

The std::vector<T> class has the following two constructors:

vector(size_type count, const T& value, const Allocator& alloc = Allocator());
template <class InputIt>
vector(InputIt first, InputIt last, const Allocator& alloc = Allocator());

When instantiated with T=size_t, these seem to be able to clash (with InputIt=size_t), but it does not happen. Why?

For clarity, here's a minimal code sample:

#include <iostream>

template <typename T>
struct A {
  A(size_t n, const T &v) { std::cout << n << " x " << v << std::endl; }
  template <typename I>
  A(I first, I last) { std::cout << first << " ... " << last << std::endl; }
};

int main() {
  size_t x = 3, y = 42;
  A<size_t> a1(x, y);    // prints 3 x 42
  A<size_t> a2(3, 42);   // prints 3 ... 42
}

EDIT: I put @jrok's version also in the example. Why is one of the constructors prioritized in a1, and the other one in a2?

Upvotes: 2

Views: 126

Answers (2)

NathanOliver
NathanOliver

Reputation: 180935

In [sequence.reqmts]/3 the standard has

[...] i and j denote iterators that meet the Cpp17InputIterator requirements and refer to elements implicitly convertible to value_­type [...]

And this requirement means that

template <class InputIt>
vector(InputIt first, InputIt last, const Allocator& alloc = Allocator());

Is only viable if InputIt is actually a input iterator or better. This means that the implementation must use SFINAE or other techniques to make this overload not considered when InputIt is not an actual iterator type. size_t is not an iterator type so the only constructor that can be used to construct the object is

vector(size_type count, const T& value, const Allocator& alloc = Allocator());

This is different from your minimal example. With your minimal example you could call

A(size_t n, const size_t &v) - non-template-specialization
// or
A(size_t first, size_t last) - template-specialization

And if by the rules of [over.ics.ref]/1 both functions are equally good since const size_t & is considered the identity transformation which means the non-template-specialization is called as those are preferred to template-specializations

Upvotes: 3

jrok
jrok

Reputation: 55415

The compiler is required, during overload resolution, to drop the template overload from the set of candidate functions when InputIt is not deduced as an iterator type (more formally, when it doesn't satisfy LegacyInputIterator concept - see note (5) on this page).

Removing overloads in user code is done with SFINAE technique, altough the compiler implementaion could use something else - they're not strictly required to use legal C++ code. For example, my (MinGW) compiler implements it with SFINAE:

template<typename _InputIterator,
           typename = std::_RequireInputIter<_InputIterator>>
    vector(_InputIterator __first, _InputIterator __last,
       const allocator_type& __a = allocator_type());

where _RequireInputIter is

 template<typename _InIter>
    using _RequireInputIter = typename
      enable_if<is_convertible<typename
        iterator_traits<_InIter>::iterator_category,
                   input_iterator_tag>::value>::type;

This requirement came into effect with C++11, so prior to it, you could in fact call the wrong constructor. If we modify your example a little:

#include <iostream>

template <typename T>
struct A {
  A(size_t n, const T &v) { std::cout << n << " x " << v << std::endl; }
  template <typename I>
  A(I first, I last) { std::cout << first << " ... " << last << std::endl; }
};

int main() {
  A<size_t> a(3, 42);
}

it now prints 3 ... 42.

Upvotes: 5

Related Questions