我正在尝试实现我自己的向量dml::vector
,其API与std::vector
相同。让我感到困惑的是insert()
函数的重载分辨率。
我想打电话给:
template<class InputIt>
void insert(iterator pos, InputIt first, InputIt last)
但失败,出现编译或链接错误。
只是删除了不必要的代码以保持简单,关键代码是:
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;
}
完整的代码是:
#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;
}
注意:insert_test()
中将dml::
替换为std::
,将编译和链接确定。我期望的是使用dml::
编译和链接 OK 并运行与使用std::
时相同的结果。
注意2:有一个类似的问题:重载分辨率如何适用于std::vectorenable_if
的看法。仅使用我的代码,如何修改它?
>更新如答案和评论中所述,std::_RequireInputIter
似乎没有正确使用。我也尝试编写自己的:
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)
{
...
}
但这仍然会导致过载解决失败。
<小时 />更新2
在 @JD ługosz 的回答中,提到了一个"称为重复析构函数",涉及new [] / delete []
和operator new[] / operator delete[]
。让我提供这个简单的片段来证明我的观点:operator new[]
只分配内存,不会调用构造函数,并且与new[]
不同。
这是代码:
#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;
}
这是输出(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
我认为问题是您的VectorIterator
类型在std::_RequireInputIter
检查中不知道是迭代器。 因此,有了该检查,它就不会使用该模板。
这似乎是您必须从库容器的std::
标头复制的内部帮助程序。 这不是一个标准的东西。
重载问题
考虑模板成员函数的基本 C++11 签名:
template <typename T> void insert (iterator pos, T first, T last)
这是为了处理任何一对迭代器而实现的,但没有任何东西告诉编译器只有某些类型可以用作此处的T
。 现在模板参数推导使它变得贪婪,因此任何对a
和b
类型相同x.insert(pos, a,b);
的调用都会导致此表单完全匹配,因此即使您的意思是a
是计数,b
是值,它也将被采用。 当您具有某种简单数字类型的向量(或具有采用单个数字参数的构造函数等)时,这是一个问题。
所以,看看cpp偏好声明的内容:
只有当
InputIt
符合LegacyInputIterator
条件时,此重载才会参与重载解析,以避免与重载 (3) 产生歧义。
将模板参数命名为InputIt
而不是T
没有任何意义;它只是一个名称。
原文中的疯狂内容,添加一个额外的未命名模板参数,是强加此约束的预概念方式。 如果你愿意,你可以阅读过时的enable_if
SFINAE在高级模板元编程中的创造性使用(我称之为ASlow Descent Into Madness)。
您的更新显示的是,当(且仅当)std::iterator_traits<InputIt>::iterator_category
产生input_iterator_tag
或更好的参数时,您才允许InputIt
参数的类型。 即双向迭代器"isa"输入迭代器等。 (顺便说一句,C++14 中的表述会更干净一些,这让我认为这个咒语是在 C++14 之前为 C++11 写的。
如果您使用来自std::vector
或std::array
的迭代器测试了您的实现,它将正常工作(假设它都是正确的)。 问题不在于您的insert
声明。 问题是您的VectorIterator
没有为其定义任何iterator_traits
,因此模板代码认为它根本不是迭代器。
同时...
for (size_t i=0; i<lhs.size(); i++) {
if (lhs[i]!=rhs[i]) return false;
只需使用std::equal
,不要使用自己的循环重新实现标准算法。
另外,我建议使用一个"嘈杂"类来估计容器,该类记录所有构造函数和析构函数调用以及地址。