Recently, I was asked this question on an interview:
Which Vector constructor will be called in the code below?
#include <iostream> class Iterator { public: Iterator(int &x): ptr_(&x) {} private: int* ptr_ = nullptr; }; template<class T> class Vector { public: Vector(size_t size, T default_value) { std::cout << "Constructor #1 called\n"; } template<class Iterator> Vector(Iterator first, Iterator last) { std::cout << "Constructor #2 called\n"; } }; int main() { auto v = Vector<int>(3, 5); }
The answer is the second constructor, because the types are the same. I do not really understand the reason. Could anybody explain why that happens?
Also, the second part of the question was to use std::enable_if
so that the first constructor would be called instead. What is the way to accomplish this?
Regarding the 1st part:
3
and 5
are literals of type int
.
In the 1st constructor, the 2nd parameter is using the template argument named T
, which main()
is specifying explicitly as int
, so 5
can be passed as-is. However, size_t
is an implementation-defined unsigned type, thus not int
, so an implicit conversion (ie, integer promotion) is required in order to pass 3
to the 1st parameter.
On the other hand, the 2nd constructor uses a template argument named Iterator
for both of its input parameters, and that template argument is being deduced as int
in this example, so no implicit conversion is needed. Both 3
and 5
can be passed as-is, so the 2nd constructor is an exact match for Vector<int>(3, 5)
.
If the 2nd constructor had intended to use the class
named Iterator
instead for its input parameters, then the template argument on the 2nd constructor is unnecessary and should be removed, eg:
template<class T>
class Vector {
public:
Vector(size_t size, T default_value) {
std::cout << "Constructor #1 called\n";
}
Vector(Iterator first, Iterator last) {
std::cout << "Constructor #2 called\n";
}
};
In which case, the 1st constructor becomes a better match for Vector<int>(3, 5)
. Even though the Iterator
class can be constructed from an int
variable, overload resolution prefers integer promotion over object construction. Also, because 3
and 5
are rvalues, and a non-const int&
reference can't bind to an rvalue, so the Iterator
class wouldn't be constructable in this example anyway.