Понимание комментария об ошибках по пункту 41 EMC++

В пункте 41 Скотт Мейерс описывает следующие два класса:

class Widget {
public:
  void addName(const std::string& newName)   // take lvalue;
  { names.push_back(newName); }              // copy it

  void addName(std::string&& newName)        // take rvalue;
  { names.push_back(std::move(newName)); }   // move it; ...

private:
  std::vector<std::string> names;
};
class Widget {
public:
  template<typename T>                            // take lvalues
  void addName(T&& newName)                       // and rvalues;
  {                                               // copy lvalues,
    names.push_back(std::forward<T>(newName)); }  // move rvalues;
  }                                               // ...
private:
  std::vector<std::string> names;
};

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

Однако в исправлениях автор комментирует другое отличие, не обсуждавшееся в книге. :

Другое поведенческое различие между (1) перегрузкой для lvalue и rvalue и (2) шаблоном, использующим универсальную ссылку (uref), заключается в том, что перегрузка lvalue объявляет свой параметр const, а подход uref — нет. Это означает, что функции, вызываемые для параметра перегрузки lvalue, всегда будут версиями const, в то время как функции, вызываемые для параметра версии uref, будут версиями const, только если передан аргумент const. Другими словами, аргументы, отличные от const lvalue, могут привести к другому поведению в дизайне перегрузки по сравнению с дизайном uref.

Но я не уверен, что понимаю это.

На самом деле, написав этот вопрос, я, наверное, понял, но я не пишу ответ, так как все еще не уверен.

Вероятно, автор имеет в виду, что когда в addName передается не-const lvalue, newName равно const в первом коде и не-const во втором коде, это означает, что если newName было передано другой функции (или была вызвана функция-член), то эта функция должна была бы принимать const параметров (или быть const функцией-членом).

Я правильно интерпретировал?

Однако я не вижу, как это влияет на конкретный пример, поскольку функция-член не вызывается для newName, и не передается функции, которая имеет разные перегрузки для const и не-const параметров (не совсем так: std::vector<T>::push_back имеет две перегрузки для const T& аргументов и T&& arguments`, но lvalue по-прежнему будет связываться только с прежней перегрузкой...).


person Enlico    schedule 06.01.2021    source источник
comment
О.Т.: Забавно, однажды я написал похожий образец для собственного использования (чтобы вспомнить, когда в сомнениях). Я не был уверен, что предпочесть пару const ref/RValue ref или шаблон в продуктивной работе. Однако есть еще одно существенное отличие: шаблонная версия может принимать больше типов, чем std::string (и вам нужно подумать, нравится вам это или нет).   -  person Scheff's Cat    schedule 06.01.2021
comment
@Scheff, правда, но все же можно настаивать на шаблонной версии и ограничить ее домен с помощью SFINAE или static_assert в теле, чтобы он принимал только std::string и/или любой другой желаемый тип.   -  person Enlico    schedule 06.01.2021
comment
Ошибки говорят о том, что в первом примере, если newName передается другой функции, она всегда будет выбирать перегрузку const. Во втором примере, если переданный параметр сам по себе не является const, то вместо константной перегрузки будет выбрана неконстантная перегрузка. Суть этой разницы в том, что это была бы молчаливая разница   -  person Michael Burr    schedule 06.01.2021
comment
@MichaelBurr, да, но первое if в вашем комментарии озадачило меня, когда я писал вопрос. Если в теле addName было что-то вроде newName.someMethod()/someFun(newName), то в первом примере всегда будет выбрано std::string::someMethod() const {/someFun(const std::string&), а во втором случае также может быть выбрано std::string::someMethod() {/someFun(std::string&), если аргумент не является const lзначение.   -  person Enlico    schedule 06.01.2021


Ответы (1)


Во втором сценарии, когда в шаблон передается const std::string lvalue

  template<typename T>
  void addName(T&& newName)
  { names.push_back(std::forward<T>(newName)); }

создание экземпляра приводит к следующему (где я удалил вызов std::forward, так как на практике он не работает)

  void addName(const std::string& newName)
  { names.push_back(newName); }

тогда как если передано std::string lvalue, то результирующий экземпляр addName будет

  void addName(std::string& newName)
  { names.push_back(newName); }

это означает, что вызывается неconst версия std::vector<>::push_back.

В первом сценарии, когда std::string lvalue передается в addName, выбирается первая перегрузка независимо от const-ности

  void addName(const std::string& newName)
  { names.push_back(newName); }

это означает, что в обоих случаях выбрана перегрузка const для std::vector<>::push_back.

person Enlico    schedule 07.01.2021