Основы C++: Ошибка перегрузки оператора шаблона — «ошибка вывода/подстановки аргумента шаблона»

Я пытался написать свой собственный векторный класс, чтобы лучше понимать шаблоны и итераторы 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();
      |               

Я думаю, что лучший вопрос здесь: «Почему нельзя вывести T?» Ответ, который вы получите на этот вопрос, будет служить вам намного дольше и дальше.

user4581301 13.03.2024 21:37

@ user4581301 Спасибо, 273K опубликовал ссылку на вывод аргументов шаблона, на которую я собираюсь взглянуть, поскольку я все еще в замешательстве даже после решения этой проблемы. Я использовал «Экскурсию по C++» Стоуструпа, но он почти не разбирается в этом.

Hersh Joshi 14.03.2024 01:22

Обзор C++ чертовски краток. Когда я нанимаю на работу новых сотрудников, работающих с C++, я довожу их до C++11 с помощью «Практики и принципов программирования Страуструпа» и использую Tour, чтобы привести их в соответствие, ну, почти в актуальном состоянии. Прошло много лет с тех пор, как я читал этот отстой, поэтому я не могу вспомнить, насколько глубоки аргументы в «Принципах», но по большинству деталей дополнительная любовь, которую дают «Принципы», ставит его намного выше «Тура».

user4581301 14.03.2024 01:47
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
3
129
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Не следует перегружать 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 является зависимым типом.

«Не следует перегружать оператор- в глобальном пространстве имен» — громкое заявление, учитывая, что наш самый канонический ресурс говорит нечто противоположное.

Yksisarvinen 13.03.2024 21:29

Объяснение, почему ваша версия работает, а глобальная шаблонная - нет, не помешает, поскольку спрашивающий говорит, что пытается изучить основы.

Wutz 13.03.2024 21:32

Это правда, когда нужно operator-(U, iterator). Вот оно operator-(iterator, iterator) и функция-член предпочтительнее.

3CxEZiVlQ 13.03.2024 21:32

Спасибо @273K. Я раньше видел ссылку на перегрузку операторов и использовал ее, но не осознавал, что она не применима к шаблонам. Теперь я понимаю, почему я видел рекомендации сделать дружественные функции перегрузчиков, определенные внутри класса, чтобы избежать этой проблемы.

Hersh Joshi 14.03.2024 01:20

Я не могу объяснить правила вывода шаблона, возможно, вывод происходит только для объекта, переданного в качестве аргумента, а не для типа вложенности. Один из способов решения этой проблемы (и тот, который используется, например, в стандартной библиотеке 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.

Hersh Joshi 14.03.2024 01:20
Ответ принят как подходящий

во-первых, я уже делал это раньше, и в любом случае я бы рекомендовал использовать класс вместо структуры, если вы хотите, чтобы оператор - работал с 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;
}

Спасибо, это очень помогло. «Ошибка в основном возникла из-за того, что компилятор не смог разрешить шаблон, потому что вы поместили его над оператор-функцией, а это означало, что он ожидал тип при вызове функции». Также да, мой фактический код заключен в класс, разделенный на частный и общедоступный и т. д. Я просто упростил его здесь.

Hersh Joshi 14.03.2024 01:18

Другие вопросы по теме