Я пытался написать свой собственный векторный класс, чтобы лучше понимать шаблоны и итераторы C++, но некоторое время застрял с этой ошибкой и был бы очень признателен за помощь.
Код дает сбой на предпоследней строке, где я вызываю перегруженный оператор -
.
Код: (Упрощенный)
#include <memory>
template <typename T>
struct MyVector {
struct iterator {
T* ptr;
iterator(T* p) : ptr(p) {}
};
std::unique_ptr<T[]> data;
size_t size;
MyVector() : data(nullptr), size(0) {}
MyVector(size_t sz) : size(sz) {
data = std::make_unique<T[]>(size);
}
iterator begin() {
return iterator(data.get());
}
iterator end() {
return iterator(data.get() + size);
}
};
template <typename T>
int operator-(typename MyVector<T>::iterator a, typename MyVector<T>::iterator b) {
return a.ptr - b.ptr;
}
int main() {
MyVector<int> mv(3);
mv.end() - mv.begin(); // fails
}
Ошибка:
so.cpp: In function ‘int main()’:
so.cpp:32:14: error: no match for ‘operator-’ (operand types are ‘MyVector<int>::iterator’ and ‘MyVector<int>::iterator’)
32 | mv.end() - mv.begin();
| ~~~~~~~~ ^ ~~~~~~~~~~
| | |
| | iterator<[...]>
| iterator<[...]>
so.cpp:26:5: note: candidate: ‘template<class T> int operator-(typename MyVector<T>::iterator, typename MyVector<T>::iterator)’
26 | int operator-(typename MyVector<T>::iterator a, typename MyVector<T>::iterator b) {
| ^~~~~~~~
so.cpp:26:5: note: template argument deduction/substitution failed:
so.cpp:32:25: note: couldn’t deduce template parameter ‘T’
32 | mv.end() - mv.begin();
|
@ user4581301 Спасибо, 273K опубликовал ссылку на вывод аргументов шаблона, на которую я собираюсь взглянуть, поскольку я все еще в замешательстве даже после решения этой проблемы. Я использовал «Экскурсию по C++» Стоуструпа, но он почти не разбирается в этом.
Обзор C++ чертовски краток. Когда я нанимаю на работу новых сотрудников, работающих с C++, я довожу их до C++11 с помощью «Практики и принципов программирования Страуструпа» и использую Tour, чтобы привести их в соответствие, ну, почти в актуальном состоянии. Прошло много лет с тех пор, как я читал этот отстой, поэтому я не могу вспомнить, насколько глубоки аргументы в «Принципах», но по большинству деталей дополнительная любовь, которую дают «Принципы», ставит его намного выше «Тура».
Не следует перегружать operator-
в глобальном пространстве имен.
#include <cstddef>
#include <memory>
template <typename T>
struct MyVector {
struct iterator {
T* ptr;
ptrdiff_t operator-(const iterator& b) const { return ptr - b.ptr; }
};
std::unique_ptr<T[]> data;
size_t size = 0;
MyVector() = default;
MyVector(size_t sz) : size(sz) { data = std::make_unique<T[]>(size); }
iterator begin() const { return {data.get()}; }
iterator end() const { return {data.get() + size}; }
};
int main() {
MyVector<int> mv(3);
mv.end() - mv.begin(); // fails
}
См. Вывод аргументов шаблона. T
в typename MyVector<T>::iterator
не подлежит вычету, iterator
является зависимым типом.
«Не следует перегружать оператор- в глобальном пространстве имен» — громкое заявление, учитывая, что наш самый канонический ресурс говорит нечто противоположное.
Объяснение, почему ваша версия работает, а глобальная шаблонная - нет, не помешает, поскольку спрашивающий говорит, что пытается изучить основы.
Это правда, когда нужно operator-(U, iterator)
. Вот оно operator-(iterator, iterator)
и функция-член предпочтительнее.
Спасибо @273K. Я раньше видел ссылку на перегрузку операторов и использовал ее, но не осознавал, что она не применима к шаблонам. Теперь я понимаю, почему я видел рекомендации сделать дружественные функции перегрузчиков, определенные внутри класса, чтобы избежать этой проблемы.
Я не могу объяснить правила вывода шаблона, возможно, вывод происходит только для объекта, переданного в качестве аргумента, а не для типа вложенности. Один из способов решения этой проблемы (и тот, который используется, например, в стандартной библиотеке gcc) — создать iterator
невложенный класс, предоставляя его только как псевдоним вложенного имени (см. в Интернете):
namespace detail {
template <typename T>
struct MyIterator {
T* ptr;
MyIterator(T* p) : ptr(p) {}
};
template <typename T>
int operator-(MyIterator<T> a, MyIterator<T> b) {
return a.ptr - b.ptr;
}
} // namespace detail
template <typename T>
struct MyVector {
using iterator = detail::MyIterator<T>;
// ... rest of implementation
};
int main() {
MyVector<int> mv(3);
mv.end() - mv.begin(); // fails
}
Лучше ли это, чем просто сделать это функцией-членом, я бы сказал, это спорно.
Плюсы заключаются в том, что он следует рекомендациям основных правил и идиом перегрузки операторов, которые являются хорошо известной вики по C++, и люди склонны придерживаться этого (хотя он также специально допускает случай создания такого члена оператора во вложенных сорт). Это также позволяет вам повторно использовать этот тип итератора в других контейнерах (например, в std::array
-подобном контейнере).
Обратной стороной является то, что вы теряете возможность сделать это по-настоящему private
, и люди в любом случае будут иметь доступ к detail::MyIterator
.
Спасибо за помощь. Я не был уверен, что один из двух способов правильный. Я придерживаюсь других упомянутых ответов только потому, что закончил это упражнение, но в дальнейшем ваш определенно выглядит намного чище и понятнее, если это так, как это делает STL.
во-первых, я уже делал это раньше, и в любом случае я бы рекомендовал использовать класс вместо структуры, если вы хотите, чтобы оператор - работал с MyVector.end() - MyVector.begin(), он должен находиться в структуре итератора, и в этом случае оператор должен иметь 1 аргумент - цель. Ошибка в основном возникла из-за того, что компилятор не смог разрешить шаблон, потому что вы поместили его над функцией-оператором, а это означало, что он ожидал тип при вызове функции. Я не изменял какой-либо ваш код, кроме того, что требовалось. чтобы это заработало, поскольку вы сказали, что это проект, который нужно изучить, вот исправленный и работающий код:
#include <iostream>
#include <memory>
template <typename T>
struct MyVector {
struct iterator {
T* ptr;
iterator(T* p) : ptr(p) {}
int operator - (const iterator tgt) const {
return this->ptr - tgt.ptr;
}
};
std::unique_ptr<T[]> data;
size_t size;
MyVector() : data(nullptr), size(0) {}
MyVector(size_t sz) : size(sz) {
data = std::make_unique<T[]>(size);
}
iterator begin() {
return iterator(data.get());
}
iterator end() {
return iterator(data.get() + size);
}
};
int main() {
MyVector<int> mv(3);
int thing = mv.end() - mv.begin();
std::cout << thing << std::endl;
}
Спасибо, это очень помогло. «Ошибка в основном возникла из-за того, что компилятор не смог разрешить шаблон, потому что вы поместили его над оператор-функцией, а это означало, что он ожидал тип при вызове функции». Также да, мой фактический код заключен в класс, разделенный на частный и общедоступный и т. д. Я просто упростил его здесь.
Я думаю, что лучший вопрос здесь: «Почему нельзя вывести
T
?» Ответ, который вы получите на этот вопрос, будет служить вам намного дольше и дальше.