¿Cuál es la restricción correcta de 'enable_if' en el setter de reenvío perfecto?

Herb Sutter's¡De vuelta a lo fundamental! Esenciales de C ++ moderno presentación en CppCon discutió diferentes opciones para pasar parámetros y comparó su desempeño versus facilidad de escritura / enseñanza. La opción 'avanzada' (que proporciona el mejor rendimiento en todos los casos probados, pero demasiado difícil de escribir para la mayoría de los desarrolladores) fue el reenvío perfecto, con el ejemplo dado(PDF, pág. 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);
    }
};

El ejemplo utiliza una función de plantilla con referencia de reenvío, con el parámetro de plantillaString restringido usandoenable_if. Sin embargo, la restricción parece ser incorrecta: parece decir que este método solo se puede usar siString tipo no es unstd::string, lo cual no tiene sentido. Eso significaría que estostd::string el miembro se puede configurar usandotodo menos unstd::string valor.

using namespace std::string_literals;

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

Una explicación que consideré fue que hay un error tipográfico simple y que la restricción estaba destinada a serstd::is_same<std::decay_t<String>, std::string>::value en lugar de!std::is_same<std::decay_t<String>, std::string>::value. Sin embargo, eso implicaría que el setter no funciona, por ejemplo,const char * y obviamente estaba destinado a trabajar con este tipo dado que ese es uno de los casos probados en la presentación.

Me parece que la restricción correcta es más como:

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);
}

permitiendo que cualquier cosa que pueda asignarse al miembro se use con el setter.

¿Tengo la restricción correcta? ¿Hay otras mejoras que se pueden hacer? ¿Hay alguna explicación para la restricción original, tal vez fuera de contexto?

También me pregunto si las partes complicadas e "imposibles de enseñar" de esta declaración son realmente tan beneficiosas. Como no estamos utilizando la sobrecarga, simplemente podemos confiar en la creación de instancias de plantilla normal:

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

Y, por supuesto, hay un debate sobre sinoexcept realmente importa, algunos dicen que no te preocupes demasiado por eso, excepto por las primitivas mover / intercambiar:

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

Tal vez con conceptos no sería irrazonablemente difícil restringir la plantilla, simplemente para mensajes de error mejorados.

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

Esto aún tendría los inconvenientes de que no puede ser virtual y que debe estar en un encabezado (aunque es de esperar que los módulos eventualmente representen ese argumento), pero esto parece bastante enseñable.

Respuestas a la pregunta(3)

Su respuesta a la pregunta