15

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

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

+0

Ну, кто-то в комнате сказал, что это ограничение неверно, и не будет принимать строковые литералы - может быть, мы можем cc @HowardHinnant? Мне это тоже не подходит. [Видео разговора] (http://youtu.be/xnqTKD8uD64?t=1h15m21s) – dyp

+0

@PiotrS .: MSVC не имеет псевдонимов, поэтому я забыл:/ –

+0

@MooingDuck Эй, VS2013 поддерживает псевдонимы и их инструменты stdlib псевдонимы C++ 14 type_trait. – bames53

ответ

3

Я думаю, что вы, вероятно, правы, но в интересах не писать «ответ», который просто «я согласен», я предлагаю это вместо этого, чтобы проверить назначение на основе правильных типов - будь то lval, RVAL, Const, независимо от:

template <class String> 
auto set_name(String&& name) 
-> decltype(name_ = std::forward<String>(name), void()) { 
    name_ = std::forward<String>(name); 
} 
+0

Это будет немного уродливее, конечно, когда вы снова добавите предложение noexcept. –

4

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

Да, право ограничение, которое было представлено на экране было is_same, не !is_same. Похоже, на вашем слайде есть опечатка.


Однако это означало бы, что сеттер не работает, например, const char *

Да, и я считаю, что это было сделано специально. Когда строковый литерал, как "foo" передаются функции, принимающего универсальной ссылки, то выведенная тип не указатель (поскольку массивы распадаются на указатели только тогда, когда пойманный в параметре шаблона по значению), это, скорее, a const char(&)[N]. Тем не менее, каждый вызов set_name с строковый литерал различной длины будет создавать новый set_name специализации, как:

void set_name(const char (&name)[4]); // set_name("foo"); 
void set_name(const char (&name)[5]); // set_name("foof"); 
void set_name(const char (&name)[7]); // set_name("foofoo"); 

Ограничение предполагается определить универсальную ссылку так, что он принимает и выводит либо только std::string типов для аргументов rvalue, или cv-std::string& для аргументов lvalue (поэтому это std::decay ed перед сравнением с std::string в этом состоянии std::is_same).


, очевидно, предназначалась для работы с этим типом, учитывая, что это один из случаев, испытанных в презентации.

Я думаю, что тестируемая версия (4) не ограничена (заметим, был назван String&& + совершенным экспедиторская), так что это может быть столь же просто, как:

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

так, что когда строковым передается, он не создает экземпляр std::string до вызова функции так же, как и не templated версии (без необходимости выделения памяти в код вызываемого только для создания временного std::string, который в конечном итоге будет перемещен в возможно предварительно выделенный пункт назначения, например):

void set_name(const std::string& name); 
void set_name(std::string&& name); 

Мне кажется, что правильное ограничение больше похож: std::enable_if_t<std::is_assignable<decltype((name_)), String>::value>>

Нет, как я уже писал, я не думаю, что намерение состояло в том, чтобы ограничить set_name принять типы поручаемые до std::string. Еще раз - этот оригинал std::enable_if должен иметь единственную реализацию функции set_name, принимающей универсальную ссылку, принимающую только значения и значения l std::string (и не более того, хотя это шаблон). В вашей версии std::enable_if передача чего-либо, не назначаемого на std::string, приведет к ошибке, независимо от того, является ли это константа или нет при попытке сделать это. Обратите внимание, что в конечном итоге мы могли бы просто переместить аргумент name в _name, если это ссылка на не-const r, поэтому проверка назначаемости бессмысленна, за исключением ситуаций, когда мы не используем SFINAE, чтобы исключить эту функцию из разрешения перегрузки в пользу другой перегрузки ,


Что такое правильный enable_if ограничение на идеальный экспедиторской инкубаторе?

template <class String, 
      class = std::enable_if_t<std::is_same<std::decay_t<String>, 
               std::string>::value>> 

или нет ограничений на все, если это не вызовет каких-либо потери производительности (так же, как проходит строковые литералы).

+0

* «Я не думаю, что цель заключалась в том, чтобы ограничить имя_таблицы, чтобы принимать типы, присваиваемые' std :: string' *. * Если мы сравним его с другими решениями, он должен по крайней мере быть неявным образом конвертируемым в 'std :: string ', чтобы разрешить те же преобразования, что и другие решения. Но это кажется неуместным, поскольку мы не * хотим * выполнить неявное преобразование перед назначением. – dyp

+0

@dyp: это хорошая причина, по которой тип ограничивается только 'std :: string', а не типами * присваиваемых * для строки, это то, что вы имеете в виду? –

+0

Ну, я имею в виду, что другие решения разрешают 'x.set_name (« hello »);', тогда как этот не делает - '' hello "' не является 'std :: string', но он неявно конвертируется в' std :: string && 'и' std :: string const & 'и' std :: string'. – dyp

1

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

Извините, что опаздывает на вечеринку. Я просмотрел свои заметки, и действительно, я виноват в том, что рекомендую Herb исходное ограничение для варианта 4 за вычетом !. Никто не идеален, поскольку моя жена, несомненно, подтвердит, и меньше всего меня. :-)

Напоминание: точка Травы (который я согласен с), это начать с простой C++ 98/03 совет

set_name(const string& name); 

и двигаться оттуда только по мере необходимости. Вариант № 4 довольно немного движется. Если мы рассматриваем вариант № 4, мы не учитываем нагрузки, запасы и распределения в критичной части приложения. Нам необходимо назначить name по номеру name_ как можно быстрее.Если мы здесь, читаемость кода гораздо менее важна, чем производительность, хотя правильность по-прежнему царит.

Отсутствие ограничений (для опции №4) Я считаю, что это немного некорректно. Если какой-либо другой код пытался сдерживать себя, может ли он назвать employee::set_name, он может получить неправильный ответ, если set_name не ограничен вообще:

template <class String> 
auto foo(employee& e, String&& name) 
-> decltype(e.set_name(std::forward<String>(name)), void()) { 
    e.set_name(std::forward<String>(name)); 
    // ... 

Если set_name ничем не ограничен и String выводящих к некоторому совершенно несвязанному типу X, приведенное выше ограничение на foo неправомерно включает в себя этот экземпляр foo в наборе перегрузки. И правильность по-прежнему царит ...

Что делать, если мы хотим присвоить единый символ name_? Скажем A. Должно ли это разрешаться? Должно ли быть злым быстро?

e.set_name('A'); 

Ну, почему бы и нет ?! std::string имеет только такой оператор присваивания:

basic_string& operator=(value_type c); 

Но обратите внимание, что нет соответствующего конструктора:

basic_string(value_type c); // This does not exist 

Поэтому is_convertible<char, string>{} является false, но is_assignable<string, char>{} является true.

Это не логическая ошибка, чтобы попытаться установить имя string с char (если вы не хотите, чтобы добавить документацию к employee, который говорит так). Таким образом, даже если исходный C++ 98 реализация/03 не позволяет синтаксис:

e.set_name('A'); 

Это действительно позволяют ту же логическую операцию менее эффективным образом:

e.set_name(std::string(1, 'A')); 

И мы имеем дело с вариант № 4, потому что мы отчаянно пытаемся оптимизировать эту вещь в максимально возможной степени.

По этим причинам я считаю, что is_assignable - лучшая черта, которая ограничивает эту функцию. И стилистически я нахожу Barry's technique for spelling this constraint quite acceptable. Поэтому здесь и идет мой голос.

Также обратите внимание, что employee и std::string здесь приведены только в разговорах Херба. Они являются stand-ins для типов вы имеете дело в ваш код. Этот совет предназначен для обобщения кода, с которым вам приходится иметь дело.

+0

Это хороший момент в том, что нужно ограничить ограничение присвоения имени 'name_', однако мне кажется, что это особый случай из-за того, что применяется неявное преобразование из' int' в 'char'. В идеале класс string мог бы запретить неявные преобразования в этом случае, но я думаю, что, по крайней мере, мы можем что-то сделать в 'employee :: set_name'. Что относительно [этого] (http://coliru.stacked-crooked.com/a/b25e0861fbeb7c00)? Или, может быть, использование фигурных скобок для этого слишком тонкое и поэтому столь же непостижимо, как ограничения SFINAE? – bames53

+0

@ bames53: Моя ошибка в использовании «int» в качестве примера «другого типа» в моем примере. Я должен был использовать 'class rutabaga' и demoed, что' rutabaga' не имеет преобразований в 'std :: string'.Однако иногда '' '' '' '' '' '' '' служа '' '' '' '' rutabaga '', и поэтому можно предположить, что 'foo' перегружается на' employee' и 'rutabaga', или что-то, что' rutabaga' неявно преобразует, например, 'vegetable', или templated параметр, представляющий «растительность». То есть с расширенным SFINAE * любое * выражение может использоваться как ограничение, даже 'e.set_name'. –

+0

Но если вы ограничиваете 'is_assignable', ваш' foo' с 'String = int' будет по-прежнему находиться в наборе перегрузки, потому что' int' (и 'double' и т. Д.) Преобразуется в' char'. –

 Смежные вопросы

  • Нет связанных вопросов^_^