Reputation: 33
I'm fairly new to C++ and as a learning exercise I'm creating my own simple vector class.
It seems to work well, except when I create a Vec object trying to use the
Vec(size_type n, const T& val = T()) constructor, it instead wants to use the templated constructor that is supposed to take two iterators/pointers, and gives me errors.
I believe that I understand why this is happening, but I can't think of a way to make it less ambiguous without changing the way the class is used. I want to keep the use the same as with a std::vector.
How can I get the proper constructor called? How does the std::vector avoid this problem?
Vec.h
//#includes: <algorithm> <cstddef> <memory>
template <class T> class Vec
{
public:
//typedefs for iterator, size_type....
//just used T* for iterator
Vec() { create(); }
Vec(const Vec& v) { create(v.begin(), v.end()); }
explicit Vec(size_type n, const T& val = T()) { create(n, val); }
template <class InputIt>
Vec(InputIt first, InputIt last) { create(first, last); }
~Vec() { uncreate(); }
T& operator[](size_type i) { return data[i]; }
const T& operator[](size_type i) const { return data[i]; }
Vec& operator=(const Vec&);
//functions such as begin(), size(), etc...
private:
iterator data;
iterator avail;
iterator limit;
std::allocator<T> alloc;
void create();
void create(size_type, const T&);
template <class InputIt>
void create(InputIt first, InputIt last)
{
data = alloc.allocate(last - first);
limit = avail = std::uninitialized_copy(first, last, data);
}
void uncreate();
void grow();
void unchecked_append(const T&);
};
template <class T>
Vec<T>& Vec<T>::operator=(const Vec& rhs)
{
if (&rhs != this)
{
uncreate();
create(rhs.begin(), rhs.end());
}
return *this;
}
template <class T> void Vec<T>::create()
{
data = avail = limit = 0;
}
template <class T> void Vec<T>::create(size_type n, const T& val)
{
data = alloc.allocate(n);
limit = avail = data + n;
std::uninitialized_fill(data, limit, val);
}
template <class T>
void Vec<T>::create(const_iterator i, const_iterator j)
{
data = alloc.allocate(j - i);
limit = avail = std::uninitialized_copy(i, j, data);
}
template <class T> void Vec<T>::uncreate()
{
if (data)
{
iterator it = avail;
while (it != data)
alloc.destroy(--it);
alloc.deallocate(data, limit - data);
}
data = limit = avail = 0;
}
//left out what I didn't think was relevant
It seems to work fine except the case I mentioned above.
main.cpp
#include "Vec.h"
main()
{
Vec<int> v1(10, 100);
}
I get errors:
||=== Build: Debug in Vec Class (compiler: GNU GCC Compiler) ===| c:\program files\codeblocks\mingw\bin\..\lib\gcc\mingw32\4.7.1\include\c++\bits\stl_uninitialized.h||In instantiation of '_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = int; _ForwardIterator = int*]':| F:\CBProjects\Accelerated C++\Programming with Classes\Vec Class\Vec.h|58|required from 'void Vec::create(InputIt, InputIt) [with InputIt = int; T = int]'| F:\CBProjects\Accelerated C++\Programming with Classes\Vec Class\Vec.h|24|required from 'Vec::Vec(InputIt, InputIt) [with InputIt = int; T = int]'| F:\CBProjects\Accelerated C++\Programming with Classes\Vec Class\main.cpp|9|required from here| c:\program files\codeblocks\mingw\bin\..\lib\gcc\mingw32\4.7.1\include\c++\bits\stl_uninitialized.h|113|error: no type named 'value_type' in 'struct std::iterator_traits'| ||=== Build failed: 1 error(s), 4 warning(s) (0 minute(s), 0 second(s)) ===|
This is my first time asking a question on here. I hope I asked properly and provided enough information. Please let me know if I made a mistake in this process, and thank you for any help you can provide.
For anyone wondering what I ended up doing. I changed the templated constructor so that it won't be invoked with arithmetic arguments(since it's only meant for iterators/pointers).
template <class InputIt>
Vec(InputIt first, InputIt last,
typename std::enable_if<!std::is_arithmetic<InputIt>::value>::type* = 0) {create(first, last); }
It seems to work fine, but I'm actually not very familiar with enable_if, so if anyone knows any dangers to this approach, please let me know.
Upvotes: 2
Views: 798
Reputation: 59811
The templated constructor is chosen since it is a better match for the arguments.
Looking at the documentation of the vector constructor you will see a note that this constructor only participates in overload resolution if the arguments satisfy the requirements of InputIterator
. This is usually achieved by disabling an overload using SFINAE. The following is how libcpp achieves that. I tried to cut away some unnecessary bits and adjusted the naming conventions to make for better readability.
A usual definition of this constructor in an actual implementation would look like this:
template <class InputIterator>
vector(InputIterator first, InputIterator last,
typename enable_if<is_input_iterator <InputIterator>::value>::type* = 0);
template <class InputIterator>
vector(InputIterator first, InputIterator last, const allocator_type& a,
typename enable_if<is_input_iterator <InputIterator>::value>::type* = 0);
I removed the unnecessary parts that further differentiates between InputIterator
and ForwardIterator
. The important part for SFINAE is the enable_if and its arguments.
Here are the necessary definitions of is_input_iterator
:
template <class Tp, class Up,
bool = has_iterator_category<iterator_traits<Tp> >::value>
struct has_iterator_category_convertible_to
// either integral_constant<bool, false> or integral_constant<bool, true>
: public integral_constant<
bool,
is_convertible<typename iterator_traits<Tp>::iterator_category, Up>::value
>
{};
// specialization for has_iterator_category<iterator_traits<Tp> >::value == false
template <class Tp, class Up>
struct has_iterator_category_convertible_to<Tp, Up, false> : public false_type {};
template <class Tp>
struct is_input_iterator
: public has_iterator_category_convertible_to<Tp, input_iterator_tag> {};
The essence of it is that it checks if a given type has an iterator_category
tag that can be converted to input_iterator_tag
.
has_iterator_category
is even more obscure:
template <class Tp>
struct has_iterator_category
{
private:
struct two {char lx; char lxx;};
template <class Up> static two test(...);
template <class Up> static char test(typename Up::iterator_category* = 0);
public:
static const bool value = sizeof(test<Tp>(0)) == 1;
};
It is used to check if a given type has a member called iterator_category
or not. If the answer is no
has_iterator_category_convertible_to
will simply default to false_type
. If this wouldn't be done a non-exisiting name would be accessed and a hard compiler error would be triggered, which is not what you want to do in SFINAE.
Upvotes: 3