I'm trying to implement my own vector dml::vector
whose API is same as std::vector
. What make me confuse is the insert()
function's overloading resolution.
I would like to call:
template<class InputIt>
void insert(iterator pos, InputIt first, InputIt last)
But failed with either compile or link error.
Just removed unnecessary code to keep it simple, the key code is:
template<class InputIt, typename = std::_RequireInputIter<InputIt>> // no matching member function for call to 'insert'
//template<class InputIt> //undefined reference to `dml::operator+(dml::VectorIterator<dml::vector<int> > const&, unsigned long)
void insert(iterator pos, InputIt first, InputIt last)
{
std::cout << "----- 3" << std::endl;
}
The complete code is:
#include <new>
#include <string.h>
#include <stdlib.h>
#include <stdexcept>
#include <iostream>
namespace dml
{
template<typename T>
void create_memory(T** data, std::size_t num_elem) {
*data = static_cast<T*>(operator new[](sizeof(T) * num_elem));
}
template<typename T>
void destroy_memory(T* data, std::size_t num_elem) {
for (std::size_t i=0; i<num_elem; i++) {
data[i].~T();
}
operator delete[](data);
}
template<typename vector_type>
class VectorIterator
{
public:
using ValueType = typename vector_type::value_type;
using PointerType = ValueType*;
using ReferenceType = ValueType&;
using DifferenceType = std::size_t;
public:
VectorIterator(PointerType ptr): ptr_(ptr) {}
VectorIterator& operator ++ () {
ptr_ ++;
return *this;
}
VectorIterator operator ++ (int) {
VectorIterator iterator = *this;
ptr_ ++;
return iterator;
}
VectorIterator& operator -- () {
ptr_ --;
return *this;
}
VectorIterator operator -- (int) {
VectorIterator iterator = *this;
ptr_ --;
return iterator;
}
bool operator == (const VectorIterator& other) {
return ptr_ == other.ptr_;
}
bool operator != (const VectorIterator& other) {
return ptr_ != other.ptr_;
}
ValueType& operator * () {
return *ptr_;
}
private:
PointerType ptr_;
template <typename T>
friend class vector;
friend VectorIterator<vector_type> operator + (const VectorIterator<vector_type>& lhs, size_t count);
};
template <typename vector_type>
VectorIterator<vector_type> operator + (const VectorIterator<vector_type>& lhs, size_t count)
{
return VectorIterator<vector_type>(lhs.ptr_ + count);
}
template <typename T>
class vector {
public:
using size_type = std::size_t;
using value_type = T;
using reference = value_type&;
using const_reference = const value_type&;
using iterator = VectorIterator<vector<T>>;
using const_iterator = const VectorIterator<vector<T>>;
using reverse_iterator = VectorIterator<vector<T>>;
public:
vector(): size_(0), capacity_(0), data_(nullptr) {}
vector(size_type count, const T& value = T())
: size_(count), capacity_(count)
{
create_memory(&data_, size_);
for (size_type i=0; i<size_; i++) {
new (&data_[i]) T (value);
}
}
//! braced-init-list
vector(std::initializer_list<T> init): size_(init.size()), capacity_(init.size())
{
create_memory(&data_, size_);
typename std::initializer_list<T>::iterator it = init.begin();
for (size_type i=0; i<size_; i++) {
new (&data_[i]) T (*it);
it ++;
}
}
//! cpoy constructor
vector(const vector& v): size_(v.size_), capacity_(v.capacity_) {
create_memory(&data_, size_);
for (size_type i=0; i<size_; i++) {
new (&data_[i]) T (v.data_[i]);
}
}
~vector() {
if (data_!=nullptr) {
for (int i=0; i<size_; i++) {
data_[i].~T();
}
operator delete[](data_);
}
}
T& operator [] (size_type pos) {
return data_[pos];
}
const T& operator [] (size_type pos) const {
return data_[pos];
}
///////////////////// Iterators //////////////////
iterator begin() noexcept {
return iterator(data_);
}
const_iterator begin() const noexcept {
return const_iterator(data_);
}
iterator end() noexcept {
return iterator(data_ + size_);
}
const_iterator end() const noexcept {
return const_iterator(data_ + size_);
}
size_type size() const {
return size_;
}
size_type capacity() const {
return capacity_;
}
iterator insert(iterator pos, const T& value)
{
std::cout << "----- 1" << std::endl;
return insert(pos, static_cast<size_type>(1), value);
}
iterator insert(iterator pos, size_type count, const T& value)
{
std::cout << "----- 2" << std::endl;
iterator it(0);
return it;
}
template<class InputIt, typename = std::_RequireInputIter<InputIt>> // no matching member function for call to 'insert'
//template<class InputIt> //undefined reference to `dml::operator+(dml::VectorIterator<dml::vector<int> > const&, unsigned long)
void insert(iterator pos, InputIt first, InputIt last)
{
std::cout << "----- 3" << std::endl;
}
private:
size_type size_; // actual size
size_type capacity_; // actual capacity
T* data_;
};
template<class T>
bool operator== (const dml::vector<T>& lhs, const dml::vector<T>& rhs)
{
if (lhs.size()!=rhs.size()) return false;
for (size_t i=0; i<lhs.size(); i++) {
if (lhs[i]!=rhs[i]) return false;
}
return true;
}
}
template<class T>
std::ostream & operator << (std::ostream & os, const dml::vector<T>& v)
{
for (int i=0; i<v.size(); i++) {
os << v[i] << ", ";
}
os << std::endl;
return os;
}
#include <vector>
template<class T>
std::ostream & operator << (std::ostream & os, const std::vector<T>& v)
{
for (int i=0; i<v.size(); i++) {
os << v[i] << ", ";
}
os << std::endl;
return os;
}
static void insert_test()
{
std::cout << "---- insert test" << std::endl;
dml::vector<int> vec(3,100);
std::cout << vec;
auto it = vec.begin();
it = vec.insert(it, 200);
std::cout << vec;
vec.insert(it,2,300);
std::cout << vec;
// "it" no longer valid, get a new one:
it = vec.begin();
dml::vector<int> vec2(2,400);
vec.insert(it+2, vec2.begin(), vec2.end()); // ! this line cause compile/link error
std::cout << vec;
int arr[] = { 501,502,503 };
vec.insert(vec.begin(), arr, arr+3);
std::cout << vec;
}
int main()
{
insert_test();
return 0;
}
Note: replace dml::
to std::
in insert_test()
, will compile and link ok. What I expect is using dml::
compile & link OK and run same result as when using std::
.
Note2: There is a similar question: How does overload resolution work for std::vector<int>::insert , but I don't really understand what people say about enable_if
. Just using my code, how can I modify it?
UPDATE
As mentioned in the answers and comments, the std::_RequireInputIter
seems not correctly used. I've also tried write my own one:
template <typename InputIterator>
using RequireInputIterator = typename std::enable_if<
std::is_convertible<typename std::iterator_traits<InputIterator>::iterator_category,
std::input_iterator_tag
>::value
>::type;
//template<class InputIt, typename = std::_RequireInputIter<InputIt>>
template<class InputIt, typename = RequireInputIterator<InputIt>>
void insert(iterator pos, InputIt first, InputIt last)
{
...
}
But this still cause overload resolution failed.
UPDATE 2
In the answer of @JDługosz , a "duplicated destructor called" is mentioned, new [] / delete []
and operator new[] / operator delete[]
involved. Let me provide this simple snippet to demonstrate my opinion: operator new[]
only allocates memory, won't call constructor, and is different with new[]
.
Here is the code:
#include <iostream>
int main() {
{
std::cout << "--- begin of case 1" << std::endl;
Entity* p = static_cast<Entity*>(operator new[](sizeof(Entity)*10));
std::cout << "--- end of case 1" << std::endl;
}
std::cout << std::endl;
{
std::cout << "--- begin of case 2" << std::endl;
Entity* q = new Entity[10];
std::cout << "--- end of case 2" << std::endl;
}
return 0;
}
Here is the output (x64 ubuntu 20.04, clang 10.0):
--- begin of case 1
--- end of case 1
--- begin of case 2
--- Entity()
--- Entity()
--- Entity()
--- Entity()
--- Entity()
--- Entity()
--- Entity()
--- Entity()
--- Entity()
--- Entity()
--- end of case 2