Трава Саттер Назад к основам! В Essentials Modern С++ презентации на 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
, некоторые говорят, что не слишком беспокоятся об этом, кроме примитивов move/swap:
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);
}
Это все равно будет иметь недостатки, что он не может быть виртуальным и что он должен быть в заголовке (хотя, надеюсь, модули в конечном итоге выведут этот спор), но это кажется довольно обучаемым.