Own vector assign implementation - c++

I'm implementing stl like vector with writing all default functions. And there is a problem that I don't understand why It calls ragne version of assign for simple types and doesn't default.
Here is the implementation code:
Vector.h
void assign(size_t count, const T& value){ // Default version
void assign(size_t count, const T& value){
if(this->_size < count){
this->allocator.deallocate(this->arr, this->_capacity);
this->arr = this->allocator.allocate(count);
this->_capacity = count;
}
for(size_t i = 0; i < count; ++i)
this->arr[i] = value;
this->_size = count;
}
template<class InputIt>
void assign(InputIt first, InputIt last){ // Range version
size_t count = std::distance(first,last);
if(this->_size < count){
this->allocator.deallocate(this->arr, this->_capacity);
this->arr = this->allocator.allocate(count);
this->_capacity = count;
}
for(size_t i = 0; first != last; i++)
this->arr[i] = *first++;
this->_size = count;
}
Main code:
Vector<int> vec;
vec.assign(5,10);
Output:
/MyVector/MyVector.h: In instantiation of ‘void Vector<T, Allocator>::assign(InputIt, InputIt) [with InputIt = int; T = int; Allocator = std::allocator]’:
../MyVector/main.cpp:52:24: required from here
../MyVector/MyVector.h:99:45: error: no matching function for call to ‘distance(int&, int&)’
size_t count = std::distance(first,last);
~~~~~~~~~~~~~^~~~~~~~~~~~
In file included from /usr/include/c++/7/bits/stl_algobase.h:66:0,
from /usr/include/c++/7/bits/char_traits.h:39,
from /usr/include/c++/7/ios:40,
from /usr/include/c++/7/ostream:38,
from /usr/include/c++/7/iostream:39,
from ../MyVector/main.cpp:1:
/usr/include/c++/7/bits/stl_iterator_base_funcs.h:138:5: note: candidate: template<class _InputIterator> constexpr typename std::iterator_traits<_Iterator>::difference_type std::distance(_InputIterator, _InputIterator)
distance(_InputIterator __first, _InputIterator __last)
^~~~~~~~
/usr/include/c++/7/bits/stl_iterator_base_funcs.h:138:5: note: template argument deduction/substitution failed:
/usr/include/c++/7/bits/stl_iterator_base_funcs.h: In substitution of ‘template<class _InputIterator> constexpr typename std::iterator_traits<_Iterator>::difference_type std::distance(_InputIterator, _InputIterator) [with _InputIterator = int]’:
../MyVector/MyVector.h:99:45: required from ‘void Vector<T, Allocator>::assign(InputIt, InputIt) [with InputIt = int; T = int; Allocator = std::allocator]’
../MyVector/main.cpp:52:24: required from here
/usr/include/c++/7/bits/stl_iterator_base_funcs.h:138:5: error: no type named ‘difference_type’ in ‘struct std::iterator_traits<int>’
In file included from ../MyVector/main.cpp:2:0:
../MyVector/MyVector.h: In instantiation of ‘void Vector<T, Allocator>::assign(InputIt, InputIt) [with InputIt = int; T = int; Allocator = std::allocator]’:
../MyVector/main.cpp:52:24: required from here
../MyVector/MyVector.h:107:36: error: invalid type argument of unary ‘*’ (have ‘int’)
this->arr[i] = *first++;
^~~~~~~~
Makefile:725: recipe for target 'main.o' failed
make: *** [main.o] Error 1
I'm using C++17

The range version is a better match for vec.assign(5, 10); with InputIt = int. You should somehow disable that overload for a template parameter that doesn't represent an input iterator.
Let's take a look at stdlibc++ implementation:
template<typename InputIt, typename = std::RequireInputIter<InputIt>>
void assign(InputIt first, InputIt last) {
M_assign_dispatch(first, last);
}
where RequireInputIter is
template<typename InputIt>
using RequireInputIter = typename enable_if<is_convertible<typename
iterator_traits<InputIt>::iterator_category, input_iterator_tag>::value>::type;
In other words, for a deduced type InputIt, iterator_traits<InputIt>::iterator_category type should be convertible into input_iterator_tag. Otherwise, that assign overload is silently excluded from the overload resolution set thanks to SFINAE.
In C++17, RequireInputIter can be simplified with _t and _v helpers:
template<typename InputIt>
using RequireInputIter = enable_if_t<is_convertible_v<typename
iterator_traits<InputIt>::iterator_category, input_iterator_tag>>;
Also note that input iterators can be used to traverse a range only once. After you call std::distance(first, last), all subsequent attempts to traverse the range are undefined behaviour unless InputIt is at least a forward iterator. For input iterators you can't determine how much space to preallocate.
That's why assign uses tag dispatch technique internally. With some simplifications it looks like this:
template<typename InputIt, typename = std::RequireInputIter<InputIt>>
void assign(InputIt first, InputIt last) {
M_assign_aux(first, last,
typename iterator_traits<InputIt>::iterator_category{});
}
There are two M_assign_aux overloads
template<typename InputIt>
void M_assign_aux(InputIt first, InputIt last, std::input_iterator_tag);
template<typename ForwardIt>
void M_assign_aux(ForwardIt first, ForwardIt last, std::forward_iterator_tag);
to do the assignment. The first one will be used for input iterators only, and the second one - for forward iterators and those derived from it, i.e. bidirectional and random access ones.

You can call a specific function using cast. Like:
assign( (size_t) 5, (const int&) 10);

Related

c++98 use iterator constructor only if InputIt is an iterator of type T

For a school project I have to implement std::vector but only using C++98 standard.
The problem is that the size constructor and iterator constructor are conflicting with each other when I call it with a signed integer, so I came up with this ( whith my own implementations of enable_if, is_same, and iterator_traits):
// Size constructor
explicit vector(
size_type count,
const T &value = T(),
const Allocator &alloc = Allocator()
) : _allocator(alloc),
_capacity(count),
_size(count),
_array(_allocator.allocate(_capacity)) {
std::fill(begin(), end(), value);
}
// Iterator constructor
template <
class InputIt
> vector(
InputIt first, InputIt last,
const Allocator &alloc = Allocator(),
typename ft::enable_if< ft::is_same< typename ft::iterator_traits< InputIt >::value_type, T >::value, int >::type = 0
) : _allocator(alloc),
_capacity(std::distance(first, last)),
_size(_capacity),
_array(_allocator.allocate(_capacity)) {
std::copy(first, last, begin());
}
But now I have a problem with my implementation of iterator_traits: when I call it with an int of course it doesn't work because int doesn't have iterator member types, but when I look at cppreference about iterator_traits, it says that If Iter does not have all five member types difference_type, value_type, pointer, reference, and iterator_category, then this template has no members by any of those names (std::iterator_traits is SFINAE-friendly) (since C++17) (until C++20) which means that the check isn't implemented before C++17, so how does the real std::vector check for Iterator validity even before C++11?
Here is the compiler error I get when calling the constructor with 2 ints:
/home/crochu/Documents/42/ft_containers/iterator_traits.hpp:22:20: error: type 'int' cannot be used prior to '::' because it has no members
typedef typename Iter::difference_type difference_type;
^
/home/crochu/Documents/42/ft_containers/vector.hpp:78:55: note: in instantiation of template class 'ft::iterator_traits<int>' requested here
typename ft::enable_if< ft::is_same< typename ft::iterator_traits< InputIt >::value_type, T >::value, int >::type = 0
^
/home/crochu/Documents/42/ft_containers/main.cpp:19:20: note: while substituting deduced template arguments into function template 'vector' [with InputIt = int]
ft::vector< int > v(5, 42);
^
In file included from /home/crochu/Documents/42/ft_containers/main.cpp:13:
In file included from /home/crochu/Documents/42/ft_containers/ft_containers.hpp:15:
/home/crochu/Documents/42/ft_containers/iterator_traits.hpp:23:20: error: type 'int' cannot be used prior to '::' because it has no members
typedef typename Iter::value_type value_type;
^
/home/crochu/Documents/42/ft_containers/iterator_traits.hpp:24:20: error: type 'int' cannot be used prior to '::' because it has no members
typedef typename Iter::pointer pointer;
^
/home/crochu/Documents/42/ft_containers/iterator_traits.hpp:25:20: error: type 'int' cannot be used prior to '::' because it has no members
typedef typename Iter::reference reference;
^
/home/crochu/Documents/42/ft_containers/iterator_traits.hpp:26:20: error: type 'int' cannot be used prior to '::' because it has no members
typedef typename Iter::iterator_category iterator_category;
^
5 errors generated.
As an example, the implementation of this constructor in libstdc++ is located in the header bits/stl_vector.h:
template<typename _InputIterator>
vector(_InputIterator __first, _InputIterator __last,
const allocator_type& __a = allocator_type())
: _Base(__a)
{
// Check whether it's an integral type. If so, it's not an iterator.
typedef typename std::__is_integer<_InputIterator>::__type _Integral;
_M_initialize_dispatch(__first, __last, _Integral());
}
This is a tag dispatch using a proto-std::integral_constant class, to one of these functions:
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 438. Ambiguity in the "do the right thing" clause
template<typename _Integer>
void
_M_initialize_dispatch(_Integer __n, _Integer __value, __true_type)
{
this->_M_impl._M_start = _M_allocate(_S_check_init_len(
static_cast<size_type>(__n), _M_get_Tp_allocator()));
this->_M_impl._M_end_of_storage =
this->_M_impl._M_start + static_cast<size_type>(__n);
_M_fill_initialize(static_cast<size_type>(__n), __value);
}
// Called by the range constructor to implement [23.1.1]/9
template<typename _InputIterator>
void
_M_initialize_dispatch(_InputIterator __first, _InputIterator __last,
__false_type)
{
_M_range_initialize(__first, __last,
std::__iterator_category(__first));
}
I'd say that's about as elegant as you could get under your constraints!

template function that uses n_copy to copy first n elements form one vector another causing a compilation error

I am using the following template function in order to copy the first n elements from one vector to another.
// Example program
#include <iostream>
#include <string>
#include <vector>
template <typename Range>
inline std::vector<typename Range::value_type> take(const Range &iRange, int nbrElements) {
std::vector<typename Range::value_type> result;
if (nbrElements > iRange.size()) {
nbrElements = iRange.size();
}
std::copy_n(iRange, nbrElements, std::back_inserter(result));
return result;
}
int main()
{
std::vector<int> source = { 1, 2, 3, 4, 5, 6, 7,};
std::vector<int> destination = take(source, 7);
return 0;
}
The problem is that I am getting the following error and I don't understand why:
In instantiation of 'std::vector<typename Range::value_type>
take(const Range&, int) [with Range = std::vector<int>; typename
Range::value_type = int]':
22:48: required from here
10:19: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
In file included from /usr/include/c++/4.9/algorithm:62:0,
from 5:
/usr/include/c++/4.9/bits/stl_algo.h: In instantiation of '_OIter std::copy_n(_IIter, _Size, _OIter) [with _IIter = std::vector<int>;
_Size = int; _OIter = std::back_insert_iterator<std::vector<int> >]':
14:62: required from 'std::vector<typename Range::value_type> take(const Range&, int) [with Range = std::vector<int>; typename
Range::value_type = int]'
22:48: required from here
/usr/include/c++/4.9/bits/stl_algo.h:804:39: error: no matching function for call to '__iterator_category(std::vector<int>&)'
std::__iterator_category(__first));
^
/usr/include/c++/4.9/bits/stl_algo.h:804:39: note: candidate is:
In file included from /usr/include/c++/4.9/bits/stl_algobase.h:65:0,
from /usr/include/c++/4.9/bits/char_traits.h:39,
from /usr/include/c++/4.9/ios:40,
from /usr/include/c++/4.9/ostream:38,
from /usr/include/c++/4.9/iostream:39,
from 2:
/usr/include/c++/4.9/bits/stl_iterator_base_types.h:201:5: note: template<class _Iter> typename
std::iterator_traits<_Iterator>::iterator_category
std::__iterator_category(const _Iter&)
__iterator_category(const _Iter&)
^
/usr/include/c++/4.9/bits/stl_iterator_base_types.h:201:5: note: template argument deduction/substitution failed:
/usr/include/c++/4.9/bits/stl_iterator_base_types.h: In substitution of 'template<class _Iter> typename
std::iterator_traits<_Iterator>::iterator_category
std::__iterator_category(const _Iter&) [with _Iter =
std::vector<int>]':
/usr/include/c++/4.9/bits/stl_algo.h:804:39: required from '_OIter std::copy_n(_IIter, _Size, _OIter) [with _IIter =
std::vector<int>; _Size = int; _OIter =
std::back_insert_iterator<std::vector<int> >]'
14:62: required from 'std::vector<typename Range::value_type> take(const Range&, int) [with Range = std::vector<int>; typename
Range::value_type = int]'
22:48: required from here
/usr/include/c++/4.9/bits/stl_iterator_base_types.h:201:5: error: no type named 'iterator_category' in 'struct
std::iterator_traits<std::vector<int> >'
There are two issues with the code. The first is that it needs to have #include <algorithm>, which defines std::copy_n, and the second is that you need to take the begin() of range.
When fixed, this is what the code looks like:
// Example program
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
template <typename Range>
inline std::vector<typename Range::value_type> take(const Range &iRange, int nbrElements) {
std::vector<typename Range::value_type> result;
if (nbrElements > iRange.size()) {
nbrElements = iRange.size();
}
std::copy_n(iRange.begin(), nbrElements, std::back_inserter(result));
return result;
}
int main()
{
std::vector<int> source = { 1, 2, 3, 4, 5, 6, 7,};
std::vector<int> destination = take(source, 7);
return 0;
}
Shorter version
We can take advantage of std::vector's range constructor to write a shorter, very efficient version of take by using std::vector's range constructor:
template <class Range, class value_t = typename Range::value_type>
std::vector<value_t> take(const Range &range, size_t count) {
// Ensure count is at most range.size()
count = std::min(count, range.size());
return std::vector<value_t>(range.begin(), range.begin() + count);
}
You are almost there, only the first std::copy_n algorithm is wrong. Change the invocation to
using std::begin;
std::copy_n(begin(iRange), nbrElements, std::back_inserter(result));
and it should work as expected. Also, #include <algorithm> is missing, and you could prevent unnecessary allocations as you know the size of the resulting sequence:
result.reserve(nbrElements); // before the call to copy_n
You could try something like that:
template <typename type>
static std::vector<type> take(const std::vector<type> &iRange, size_t nbrElements)
{
if (nbrElements > iRange.size())
{
nbrElements = iRange.size();
}
std::vector<type> result;
result.insert(result.end(), iRange.begin(), iRange.begin() + nbrElements);
return result;
}
Edit:
You can also return vector directly:
return std::vector<type>(iRange.begin(), iRange.begin() + nbrElements);

const arguments in std::remove_if

I'm going to remove elements from a list of pairs. When I'm using a pair like
std::pair<const int, bool>
I get the following compilation error:
In file included from /usr/local/include/c++/6.1.0/utility:70:0,
from /usr/local/include/c++/6.1.0/algorithm:60,
from main.cpp:1:
/usr/local/include/c++/6.1.0/bits/stl_pair.h: In instantiation of
'std::pair<_T1, _T2>& std::pair<_T1, _T2>::operator=(std::pair<_T1,
_T2>&&) [with _T1 = const int; _T2 = bool]':
/usr/local/include/c++/6.1.0/bits/stl_algo.h:868:16: required from
'_ForwardIterator std::__remove_if(_ForwardIterator, _ForwardIterator,
_Predicate) [with _ForwardIterator = std::_List_iterator > _Predicate =
__gnu_cxx::__ops::_Iter_pred&)> >]'
/usr/local/include/c++/6.1.0/bits/stl_algo.h:936:30: required from
'_FIter std::remove_if(_FIter, _FIter, _Predicate) [with _FIter =
std::_List_iterator > _Predicate =
main()::&)>]'
main.cpp:17:32: required from here
/usr/local/include/c++/6.1.0/bits/stl_pair.h:319:8: error: assignment
of read-only member 'std::pair::first'
first = std::forward(__p.first);
This is the sample code:
int main()
{
int id = 2;
std::list< std::pair <const int, bool> > l;
l.push_back(std::make_pair(3,true));
l.push_back(std::make_pair(2,false));
l.push_back(std::make_pair(1,true));
l.erase(std::remove_if(l.begin(), l.end(),
[id](std::pair<const int, bool>& e) -> bool {
return e.first == id; }));
for (auto i: l) {
std::cout << i.first << " " << i.second << std::endl;
}
}
I know that (please correct me If I am wrong):
I will have exactly the same problem as long as there is constness in any element of the list, for example, a list <const int> will also return a compilation error.
If I remove the const in the first element of the pair the code will work.
The more elegant and efficient way to do it is by using the remove_if list method, like this:
l.remove_if([id](std::pair<const int, bool>& e) -> bool {
return e.first == id; });
but my question is, what are exactly the inner workings of std::remove_if that impose the elements of the container not being const?
The general std::remove_if shuffles item values around to put the logically erased values at the end of the sequence (it's typically used in combination with member function erase to actually remove the logically erased values). It can't do that shuffling when an item isn't copyable or movable. Instead use std::list::remove_if.
If you look at the type and iterator requirements of std::remove_if, you can see that the implementation must be similar to the following (from the link above):
template<class ForwardIt, class UnaryPredicate>
ForwardIt remove_if(ForwardIt first, ForwardIt last, UnaryPredicate p)
{
first = std::find_if(first, last, p);
if (first != last)
for(ForwardIt i = first; ++i != last; )
if (!p(*i))
*first++ = std::move(*i);
return first;
}
I.e., the algorithm assumes only that the iterators have forward capabilities, and elements are moveable, and it moves elements around. Of course, moves can't be done on const objects.

forward_list: assign(_InputIterator __first, _InputIterator __last) / assign(size_type __n, const _Tp& __val)

I have implemented a subset of the forward_list and wanted to test the method assign(size_type __n, const _Tp& __val) but I get a compiler error because the compiler wants to call the method assign(_InputIterator __first, _InputIterator __last) instead.
I have written the following snippet, just to illustrate the problem:
test.h
#ifndef TEST_H
#define TEST_H
#include <utility> // Just to get the std::size_t
template<typename _Tp>
class forward_list {
public:
typedef std::size_t size_type;
void assign(size_type n, const _Tp& val)
{
printf("%s\n", __PRETTY_FUNCTION__);
}
template<typename _InputIterator>
void assign(_InputIterator first, _InputIterator last)
{
printf("%s\n", __PRETTY_FUNCTION__);
}
};
#endif // TEST_H
test.cpp
#include <stdlib.h>
#include <stdio.h>
#include "test.h"
int main()
{
forward_list<int> l;
l.assign(10, 5);
return 0;
}
The output of the execution is:
void forward_list<_Tp>::assign(_InputIterator, _InputIterator) [with _InputIterator = int; _Tp = int]
I would like to have the method assign(size_type __n, const _Tp& __val) called.
Compiler version (just in case it matters): g++ (Debian 4.7.2-5) 4.7.2
I have used similar signatures to the signatures used in the std::forward_list and, with the following code snippet (using the STL):
std::forward_list<int> l;
l.assign(10, 5);
The compiler knows that it has to call assign(size_type __n, const _Tp& __val) and doesn't get confused. What am I missing?
When you call l.assign(10, 5);, there are two viable overloads:
void assign(size_type n, const int& val)
template <>
void assign(int first, int last)
When we say that non-template functions are preferred to template functions, that is only true if the two have indistinguishable conversion sequences. But in this case, the function template will match exactly (both of your arguments are int, no conversion necessary), while the non-template will have to undergo promotation (have to promote 10 from int to size_t). So that's why the function template overload is preferred.
As to how to fix it, you just need to make the template not a viable overload. That involves writing a type_trait for input iterator, which using void_t is not hard:
template <typename... >
using void_t = void;
template <typename T, typename = void>
struct is_input_iterator : std::false_type { };
template <typename T>
struct is_input_iterator<T, void_t<
decltype(std::declval<T>() == std::declval<T>()),
decltype(std::declval<T>() != std::declval<T>()),
decltype(*std::declval<T>()),
decltype(++std::declval<T>()),
decltype(std::declval<T>()++)
>> : std::true_type { };
And then require is_input_iterator:
template <typename _InputIterator,
typename = std::enable_if_t<is_input_iterator<_InputIterator>::value>>
void assign(_InputIterator first, _InputIterator last);
There are lots of other ways to do this sort of thing, I just happen to like void_t. Regardless of which way you do it, you have to ensure that the template simply isn't viable.
The assign overload that you want to be called takes an unsigned integral type as the first argument, but you're passing it two signed integers. If you change the call to
l.assign(10U, 5); // make the first argument unsigned
the first assign overload is called. But clearly, this is not the right solution to your problem in general.
You need to constrain the assign template so that it is only viable when the type of the arguments satisfy requirements for iterators. One way to do this is to inspect iterator_traits for the type involved. If the iterator_category satisfies the requirements of an InputIterator, then the function template can be used.
template<typename InputIterator>
typename std::enable_if<
std::is_base_of<std::input_iterator_tag,
typename std::iterator_traits<InputIterator>::iterator_category
>::value
>::type
assign(InputIterator first, InputIterator last)
{
printf("%s\n", __PRETTY_FUNCTION__);
}
Live demo
Note that technically the above solution isn't guaranteed to work before C++17 (or whatever it'll be called) because iterator_traits isn't required to be SFINAE friendly until then, and it could result in a hard error instead of substitution failure. But chances are your implementation's iterator_traits is already SFINAE friendly, and you won't run into any issues.
size_t isn't guaranteed to be included by <utility>, use one of the headers listed on the linked page.
Don't use identifiers that begin with an underscore and are followed by an uppercase characters, those are reserved for the implementation.

Template functions in C++ for immutable arrays

I'm trying to create a template function that will return true if a value from an immutable array exists, and false otherwise. Here is the code:
#include <algorithm>
using std::find;
#include <vector>
using std::vector;
template<class T>
bool contains(const T *arr, int size, T val) {
vector<T> dest(arr, arr + size);
T *p = find(dest.begin(), dest.end(), val);
if (p == arr + size)
return false;
else
return true;
}
Every time I compile against a simple test program I get these errors:
main.cpp:10: instantiated from 'void testit(const T*, int, T) [with T = char]'
main.cpp:37: instantiated from here
main.cpp:69: error: cannot convert '__gnu_cxx::__normal_iterator<char*, std::vector<char, std::allocator<char> > >' to 'char*' in initialization
main.cpp: In function 'bool contains(const T*, int, T) [with T = int]':
main.cpp:10: instantiated from 'void testit(const T*, int, T) [with T = int]'
main.cpp:43: instantiated from here
main.cpp:69: error: cannot convert '__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >' to 'int*' in initialization
main.cpp: In function 'bool contains(const T*, int, T) [with T = std::string]':
main.cpp:10: instantiated from 'void testit(const T*, int, T) [with T = std::string]'
main.cpp:44: instantiated from here
(All of those errors are on the line that contains the find function)
What is the best way to go about this? Is the vector even neccessary?
Is the vector even neccessary?
No.
template<class T>
bool contains(const T *arr, int size, T val) {
return std::find(arr, arr + size, val) != arr + size;
}
If the argument is not an actual, complete array T.C.'s answer's solid (though taking the value by reference is a good idea), but if you only want to support entire arrays you can have something a smidge easier to use, with the array size extracted from the array's type:
template <class T, size_t N>
inline bool contains(const T(&arr)[N], const T& val)
{
return std::find(arr, arr + N, val) != arr + N;
}
Of course you can have both versions overloaded.
error: cannot convert '__gnu_cxx::__normal_iterator > >' to 'char*' in initialization
This is telling you the return type you have is not correct for std::find.
For the code you have, you should have something like the following.
template<class T>
bool contains(const T *arr, int size, T val)
{
vector<T> dest(arr, arr + size);
vector<T>::iterator p = find(dest.begin(), dest.end(), val);
return p != dest.end();
}
Is the vector even neccessary?
No, it could be simplified as follows.
template<class T>
bool contains(const T *arr, int size, T val)
{
const T* end = arr + size;
return find(arr, end, val) != end;
}