Какое правильное ограничение `enable_if` для установщика совершенной пересылки?

Херб СаттерсВернуться к истокам! Основы современного C ++ В презентации на CppCon обсуждались различные варианты передачи параметров и сравнивались их производительность и простота написания / обучения. Опция 'advanced' (обеспечивающая наилучшую производительность во всех протестированных случаях, но слишком сложная для написания большинством разработчиков) была идеальной пересылкой с примером, приведенным(PDF, стр. 28):

class employee {
    std::string name_;

public:
    template <class String,
              class = std::enable_if_t<!std::is_same<std::decay_t<String>,
                                                     std::string>::value>>
    void set_name(String &&name) noexcept(
      std::is_nothrow_assignable<std::string &, String>::value) {
        name_ = std::forward<String>(name);
    }
};

В примере используется функция шаблона со ссылкой для пересылки с параметром шаблонаString ограничено использованиемenable_if, Однако ограничение представляется неправильным: кажется, говорят, что этот метод может быть использован только еслиString тип неstd::string, что не имеет смысла. Это будет означать, что этоstd::string элемент может быть установлен с помощьювсе, кроме std::string значение.

using namespace std::string_literals;

employee e;
e.set_name("Bob"s); // error

Одним из объяснений, которое я рассмотрел, было то, что есть простая опечатка, и ограничение должно было бытьstd::is_same<std::decay_t<String>, std::string>::value вместо!std::is_same<std::decay_t<String>, std::string>::value, Однако это будет означать, что сеттер не работает, например, сconst char * и очевидно, что он предназначен для работы с этим типом, учитывая, что это один из случаев, протестированных в презентации.

Мне кажется, что правильное ограничение больше похоже на:

template <class String,
          class = std::enable_if_t<std::is_assignable<decltype((name_)),
                                                      String>::value>>
void set_name(String &&name) noexcept(
  std::is_nothrow_assignable<decltype((name_)), String>::value) {
    name_ = std::forward<String>(name);
}

разрешить использовать все, что может быть назначено члену, с установщиком.

У меня есть правильное ограничение? Есть ли другие улучшения, которые можно сделать? Есть ли объяснение первоначальному ограничению, возможно, оно было вырвано из контекста?

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

template <class String>
void set_name(String &&name) noexcept(
  std::is_nothrow_assignable<decltype((name_)), String>::value) {
    name_ = std::forward<String>(name);
}

И, конечно, есть некоторые споры о том,noexcept действительно имеет значение, некоторые говорят, что не стоит беспокоиться об этом, за исключением примитивов перемещения / обмена:

template <class String>
void set_name(String &&name) {
    name_ = std::forward<String>(name);
}

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

template <class String>
  requires std::is_assignable<decltype((name_)), String>::value
void set_name(String &&name) {
    name_ = std::forward<String>(name);
}

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

Ответы на вопрос(3)

Ваш ответ на вопрос