Почему определение оператора = (а не объявление) должно быть написано, когда подходящий шаблон легко доступен

У меня есть следующий самодостаточный пример пользовательского номера С++ для отслеживания построек и разрушений.

#include<iostream>

class number;
template<typename T>
double get_value(T const&);
template<typename T>
double get_value(T const& x  ){ return (double)x; }
template<typename T> requires std::is_same_v<T,number>
double get_value(T const& rhs){ return rhs.get(); }

class number{
private:
  const size_t index;
  double value;

  static size_t get_index(){ static size_t count=0; return count++; }

public:
  number():
    index(get_index()),
    value(0)
  {
    std::cout<<"number_"<<index<<"::number(void).\n";
  }
  ~number(){
    std::cout<<"number_"<<index<<"::~number()\n";
  }

  double const& get()const{ return value; }
  void print(std::ostream& ooo=std::cout)const {
    ooo<<"number_"<<index<<"_"<<value<<"\n";
  }

  // line A:
  number const& operator=(number const& rhs){
    return this->operator=<number>(rhs);
  };

  // line B:
  //number const& operator=(number const&);

  template<typename T>
  number(T const& rhs):
    index( get_index() ),
    value( get_value<T>(rhs) )
  {
    std::cout<<"number_"<<index<<"::number(T).\n";
  }

  // line C:
  template<typename T>
  number const& operator= (T const& rhs){
    value =get_value<T>(rhs);
    return *this;
  }
  template<typename T>
  number const& operator+= (T const& rhs){
    value+=get_value<T>(rhs);
    return *this;
  }
};

template<typename T1, typename T2>
  requires std::is_same_v<T1,number> or std::is_same_v<T2,number>
number operator+(T1 const& x1, T2 const& x2){
  return number(get_value(x1)+get_value(x2));
}

int main(){
  number d0;
  d0=0;

  number d1=1;
  d1=d0;

  number d2=d1;
  d2=d1+d2;

  number d3=d1+d2;
  d3=d1+2;
  d3=1+d2;
}

скомпилирован с использованием g++ main.cpp -std=c++20 с любой последней версией компилятора.

Вопрос

Почему я не могу обменять линию А на линию Б?

Ожидание

Я ожидал, что строка B сообщит компилятору на этапе разрешения оператора, что указанный оператор (строка B) существует. На этапе разрешения компилятор ищет подходящее определение указанного объявления оператора (строка B) и находит его в строке C.

Фактический результат

Вместо этого компилятор указывает undefined reference to `number::operator=(number const&)'.

B — нешаблонная функция, C — шаблон, поэтому это совершенно отдельное объявление.

Alan Birtles 20.08.2024 14:11

Строка C (по шаблону) не является подходящим определением для декларации в строке B (не по шаблону). А поскольку строка C является шаблоном, она в любом случае будет создана только при использовании.

wohlstad 20.08.2024 14:11

@AlanBirtles Правильно ли я понимаю, что компиляция не удалась, потому что шаблон является частью подписи объекта? Я думал, что компилятор проверяет кандидатов на шаблоны и только после этого просматривает полученные подписи. Так это было мое заблуждение?

user23311233 20.08.2024 14:17
operator=(number const&) имеет подпись, отличную от operator=<number>(number const&).
Eljay 20.08.2024 15:12

@user23311233 - Также шаблон может быть оператором присваивания, но не может быть специальным оператором присваивания копирования, поскольку его подпись строго регламентирована.

BoP 20.08.2024 16:25

«У меня есть следующий самодостаточный пример специального номера C++ для отслеживания построек и разрушений». -- Не могли бы вы упростить его до самодостаточного примера класса C++, для которого «определение оператора = (а не объявление) должно быть написано, когда подходящий шаблон легко доступен»? Удалите из определения практически все, кроме строк A, B и C (соответствующим образом упрощенных до ничего не делающих функций).

JaMiT 20.08.2024 17:17

«Почему я не могу обменять линию А на линию Б?» -- Может быть, лучше спросить: «Почему я не могу отказаться от строки А?» Какую выгоду вы надеетесь получить от наличия линии Б (или линии А)?

JaMiT 20.08.2024 17:21

В духе упрощения, будет ли следующий код демонстрировать ваш вопрос без лишнего шума? Строка A: // void foo(int) {} Строка B: void foo(int); Строка C: template <class T> void foo(T) {} Обертка: int main() {foo(1);} (Извлеките соответствующие фрагменты из определения класса, позволяя удалить класс, и переименуйте operator= во что-то общее.) Чем меньше отвлекающих факторов в вашем коде , тем лучше.

JaMiT 20.08.2024 17:26
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
8
115
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

В вашем классе есть оператор присваивания копирования, либо строка A, либо B, либо, при отсутствии таковых, его сгенерировал компилятор. В вашем классе также есть оператор присваивания шаблона. При прочих равных условиях нешаблонная функция подойдет лучше, чем шаблонная. Поэтому, когда вы предоставляете только объявление назначения копии, оно выбирается компилятором, и компоновщик справедливо жалуется, что его определение не найдено.

Ответ принят как подходящий

Шаблонная функция (или метод) — это рецепт создания функций.

Объявление функции (или метода) — это одновременно инструкция о том, как отправить вызов функции, и обещание разработчика, что вы реализуете это позже.

Функция шаблона

template<class T>
void foo(T);

не является функцией; его создание foo<int> не то же самое, что:

void foo(int);

Цель этого правила — позволить вам использовать механизм перегрузки для маршрутизации конкретных случаев из реализации шаблона в версии для конкретного типа.

Таким образом, ваш foo<T> может вызвать convert_to_int(t) и перенаправить работу обратно на foo(int); и звонок convert_to_int(int) может быть пессимизацией.

Кроме того, поскольку специальные члены являются сверхспециальными и сверхмагическими в том, как они используются и как создаются экземпляры, было решено, что шаблон никогда не может считаться реализацией специальной функции-члена (конструкция копирования, конструкция перемещения, копирование назначения и перемещение назначения).

Это было сделано для того, чтобы избежать случайного копирования класса только потому, что вы написали

template<class...Ts>
my_class(Ts&&...ts)

Чтобы изменить способ «копирования» вашего класса, вам нужно создать нешаблон my_class(my_class const&), а не полагаться на функцию шаблона, которая сделает это за вас.

Итак, (а) это не будет работать для неспециальных функций-членов, и (б) это конкретно не работает для специальных функций-членов как преднамеренной части стандарта.

...

Кстати, я бы крайне осторожен с тем, что делаете вы. Семантика того, что означают = и копирование, обычно должна быть простой; у вас нет, потому что a=b может копировать или не копировать определенные части состояния b в a.

Вы также глубоко внедряете бизнес-логику в свой класс.

Я бы сам следовал правилу 0. По правилу 0 мы размещаем классы со специальными функциями-членами как можно дальше от конечного пользователя.

struct creation_count_index {
  static int get_index() {
    static int current = 0;
    return current++;
  }
  const int index = get_index();
  creation_count_index(creation_count_index const&){}
  creation_count_index(creation_count_index &&){}
  creation_count_index& operator=(creation_count_index const&)&{return *this;}
  creation_count_index& operator=(creation_count_index &&)&{return *this;}
  ~creation_count_index()=default;
};

теперь этот creation_count_index можно поместить в другой класс, и он придаст семантическое значение вашему индексу, причем другой класс не должен знать, как он работает.

class number{
private:
  creation_count_index index;
  double value;
public:
  number(number const&)=default;
  number(number &&)=default;
  number& operator=(number const&)& =default;
  number& operator=(number &&)& =default;
};

ваш путь get_value может сосуществовать с указанным выше. Дело в том, что вам больше не нужно вообще возиться с index, он «просто делает правильные вещи».

Я бы предложил избегать термина «функция шаблона». По крайней мере, это неоднозначно. См. stackoverflow.com/a/1117767/580083.

Daniel Langr 20.08.2024 20:11

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

Использование SFINAE в конструкторе, чтобы проверить, существует ли конструктор типа члена
Использование if-constexpr и концепций для обнаружения экземпляра определенного типа расширенной политики
Почему квалификатор const игнорируется при применении к выведенной ссылке Lvalue в C++?
Создание декартова произведения на основе аргумента шаблона целочисленного диапазона
Почему выведение std::call_once не удалось и возникла ошибка «не удалось вывести параметр шаблона ‘_Callable’»
Невозможно получить доступ к специализации класса шаблона через универсальную ссылку
Вывести класс шаблона с уменьшенным количеством параметров шаблона
Как добавить собственный класс CSS в навигационное меню WooCommerce MyAccount?
Руководства по выводу кортежей в C++17 (CTAD): неявно генерируемые и определяемые пользователем
Как ограничить функцию шаблона определенными типами?