Herb Sutter's Назад к основам! Essentials of Modern C++ презентация на CppCon обсудила различные варианты передачи параметров и сравнила их производительность с легкостью написания/обучения. «Продвинутый» вариант (обеспечения максимальной производительности во всех случаях испытания, но слишком трудный для большинства разработчиков, чтобы писать) был совершен экспедиторский, на примере данного (PDF, pg. 28):Какое правильное ограничение `enable_if` на идеальном устройстве пересылки?
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);
}
Это будет по-прежнему имеют недостатки, которые не могут быть виртуальными, и что он должен быть в заголовке (хотя мы надеемся, модули, в конечном счете делают это спорное), но это кажется довольно понятливый.
Ну, кто-то в комнате сказал, что это ограничение неверно, и не будет принимать строковые литералы - может быть, мы можем cc @HowardHinnant? Мне это тоже не подходит. [Видео разговора] (http://youtu.be/xnqTKD8uD64?t=1h15m21s) – dyp
@PiotrS .: MSVC не имеет псевдонимов, поэтому я забыл:/ –
@MooingDuck Эй, VS2013 поддерживает псевдонимы и их инструменты stdlib псевдонимы C++ 14 type_trait. – bames53