I got very weird problem
for code like this
template <typename T>
struct A{
explicit A(unsigned int size = 0, const T &t = T())
{
}
template <typename InputIterator>
A(InputIterator first, InputIterator last) {
for(;first != last ; ++first)
{
*first; //do something with iterator
}
}
};
when I for example define
A<int> a(10,10);
the second constructor for iterators is used instead of first one. How then vectors constructors work, when they looks preety the same?
explicit vector (size_type n, const value_type& val = value_type(),
const allocator_type& alloc = allocator_type());
template <class InputIterator>
vector (InputIterator first, InputIterator last,
const allocator_type& alloc = allocator_type());
And I can make vector v(10,10) without having any troubles.
PS I got error like this
temp.cpp: In instantiation of ‘A<T>::A(InputIterator, InputIterator) [with = int; T = int]’:
temp.cpp:17:15: required from here
temp.cpp:12:4: error: invalid type argument of unary ‘*’ (have ‘int’)
The constructor method is a special method of a class for creating and initializing an object instance of that class.
Constructor in C++ is a special method that is invoked automatically at the time of object creation. It is used to initialize the data members of new objects generally. The constructor in C++ has the same name as the class or structure. Constructor is invoked at the time of object creation.
A default constructor is a constructor that either has no parameters, or if it has parameters, all the parameters have default values.
There are two ways to initialize a class object: Using a parenthesized expression list. The compiler calls the constructor of the class using this list as the constructor's argument list. Using a single initialization value and the = operator.
The reason the compiler chooses the second constructor in case of your A
is simple: your 10
is a value of signed type int
, while size_type
is some unsigned integer type. This means that 10
has to be converted to that unsigned integer type. The need for that conversion is what makes the first constructor to lose the overload resolution to the second constructor (which is an exact match for InputIterator = int
). You can work around this problem by doing
A<int> a(10u, 10);
This eliminates the need for int -> unsigned
conversion and makes the first constructor win the overload resolution through "non-template is better than template" clause.
Meanwhile, the reason it works differently with std::vector
is that the language specification gives special treatment to the constructors of standard sequences. It simply requires that std::vector
constructor invocation with two integers of the same type as arguments is somehow "magically" resolved to the first constructor from your quote (i.e. the size-and-initializer constructor). How each specific implementation achieves that is up to the implementation. It can overload the constructor for all integer types. It can use a functionality similar to enable_if
. It can even hardcode it into the compiler itself. And so on.
This is how it is stated in C++03, for example
23.1.1 Sequences
9 For every sequence defined in this clause and in clause 21:
— the constructor
template <class InputIterator>
X(InputIterator f, InputIterator l, const Allocator& a = Allocator())
shall have the same effect as:
X(static_cast<typename X::size_type>(f),
static_cast<typename X::value_type>(l), a)
if InputIterator is an integral type
C++11, takes it even further by approaching it from a different angle, although the intent remains the same: it states that if InputIterator
does not qualify as input iterator, then the template constructor should be excluded from overload resolution.
So, if you want your class template A
to behave the same way std::vector
does, you have to deliberately design it that way. You can actually take a peek into the standard library implementation on your platform to see how they do it for std::vector
.
Anyway, a low-tech brute-force solution would be to add a dedicated overloaded constructor for int
argument
explicit A(unsigned int size = 0, const T &t = T())
{ ... }
explicit A(int size = 0, const T &t = T())
{ ... }
That might, of course, imply that you'll eventually have to add overloads for all integer types.
A better solution, which I already mentioned above, would be to disable the template constructor for integer arguments by using enable_if
or similar SFINAE-based technique. For example
template <typename InputIterator>
A(InputIterator first, InputIterator last,
typename std::enable_if<!std::is_integral<InputIterator>::value>::type* = 0)
Do you have C++11 features available to you in your compiler?
When InputIterator
isint
, the instantiated
A(int first, int last)
is a better match than the instantiated
explicit A(unsigned int size = 0, const int &t = int())
due to the first argument being unsigned
. A<int> a((unsigned int)10,10)
should call the constructor you expect. You can also use SFINAE to prevent a match unless the constructor really is being passed two iterators to T
:
#include <iostream>
using namespace std;
template <typename T>
struct A{
explicit A(unsigned int size = 0, const T &t = T())
{
cout << "size constructor for struct A" << endl;
}
template <class I>
using is_T_iterator = typename enable_if<is_same<typename iterator_traits<I>::value_type, T>::value, T>::type;
template <typename InputIterator>
A(InputIterator first, InputIterator last, is_T_iterator<InputIterator> = 0) {
cout << "iterator constructor for struct A" << endl;
for(;first != last ; ++first)
{
*first; //do something with iterator
}
}
};
int main()
{
A<int>(10,10);
A<int>((int*)0,(int*)0);
//A<int>((char*)0,(char*)0); //<-- would cause compile time error since (char *) doesn't dereference to int
return 0;
}
If the condition that both arguments are iterators to T
is too strict, there are looser formulations. For instance, you can guarantee that both arguments are iterators. You could go further (but not quite as far as the example above) and ensure that they "point" to a type that is convertible to T
(using std::is_convertible
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With