С выпущенным GCC 4.8.0 у нас есть компилятор, который поддерживает автоматическое определение типа возвращаемого значения, часть C ++ 14. С помощью -std=c++1y
я могу сделать это:
auto foo() { //deduced to be int
return 5;
}
Мой вопрос: когда мне следует использовать эту функцию? Когда это необходимо и когда это делает код более чистым?
Сценарий 1
Первый сценарий, который я могу придумать, всегда возможен. Каждая функция, которую можно написать таким образом, должна быть. Проблема в том, что это не всегда может сделать код более читабельным.
Сценарий 2
Следующий сценарий - избегать более сложных возвращаемых типов. В качестве очень легкого примера:
template<typename T, typename U>
auto add(T t, U u) { //almost deduced as decltype(t + u): decltype(auto) would
return t + u;
}
Я не верю, что это когда-либо будет проблемой, хотя полагаю, что наличие явной зависимости типа возвращаемого значения от параметров в некоторых случаях может быть более ясным.
Сценарий 3
Затем, чтобы предотвратить избыточность:
auto foo() {
std::vector<std::map<std::pair<int, double>, int>> ret;
//fill ret in with stuff
return ret;
}
В C ++ 11 мы иногда можем просто return {5, 6, 7};
вместо вектора, но это не всегда работает, и нам нужно указать тип как в заголовке функции, так и в теле функции. Это чисто избыточно, и автоматический вывод типа возвращаемого значения избавляет нас от этой избыточности.
Сценарий 4
Наконец, его можно использовать вместо очень простых функций:
auto position() {
return pos_;
}
auto area() {
return length_ * width_;
}
Однако иногда мы можем смотреть на функцию, желая узнать точный тип, и, если он там не указан, нам нужно перейти к другой точке кода, например, где pos_
объявлено.
Вывод
С учетом этих сценариев, в каких из них на самом деле окажется ситуация, когда эта функция будет полезна для очистки кода? А как насчет сценариев, которые я здесь не упомянул? Какие меры предосторожности следует предпринять перед использованием этой функции, чтобы она меня не укусила позже? Есть ли что-то новое в этой функции, что невозможно без нее?
Обратите внимание, что несколько вопросов предназначены для помощи в поиске точек зрения, с которых можно на них ответить.
->decltype(t+u)
автоматическим вычетом убивает SFINAE.Ответы:
С ++ 11 вызывает аналогичные вопросы: когда использовать вывод типа возвращаемого значения в лямбдах, а когда использовать
auto
переменные.Традиционный ответ на вопрос в C и C ++ 03 звучал так: «За пределами операторов мы делаем типы явными, внутри выражений они обычно неявны, но мы можем сделать их явными с помощью приведения типов». C ++ 11 и C ++ 1y вводят инструменты вывода типа, чтобы вы могли не использовать тип в новых местах.
Извините, но вы не собираетесь решать эту проблему заранее, устанавливая общие правила. Вам нужно взглянуть на конкретный код и решить для себя, помогает ли он читабельности указывать типы повсюду: лучше ли для вашего кода указывать «тип этой вещи - X» или лучше для ваш код, чтобы сказать: «Тип этой вещи не имеет отношения к пониманию этой части кода: компилятор должен знать, и мы, вероятно, могли бы это решить, но нам не нужно говорить об этом здесь»?
Поскольку «читабельность» не определяется объективно [*] и, кроме того, зависит от читателя, вы несете ответственность как автор / редактор фрагмента кода, который не может быть полностью удовлетворен руководством по стилю. Даже в той степени, в которой руководство по стилю определяет нормы, разные люди предпочтут разные нормы и будут склонны находить все незнакомое «менее читаемым». Таким образом, удобочитаемость конкретного предложенного правила стиля часто можно оценить только в контексте других действующих правил стиля.
Все ваши сценарии (даже первый) найдут применение чьему-то стилю кодирования. Лично я считаю, что второй вариант использования является наиболее убедительным, но даже в этом случае я ожидаю, что он будет зависеть от ваших инструментов документации. Не очень полезно документировать, что тип возвращаемого значения шаблона функции - это
auto
, в то время как его документированиеdecltype(t+u)
создает опубликованный интерфейс, на который можно (надеюсь) положиться.[*] Иногда кто-то пытается сделать какие-то объективные измерения. В той небольшой степени, в которой кто-либо когда-либо дает какие-либо статистически значимые и общеприменимые результаты, они полностью игнорируются работающими программистами в пользу авторских инстинктов того, что «читабельно».
источник
1.0 + 27U
мы утверждаем последнее, когда мы пишем,(double)1.0 + (double)27U
мы утверждаем первое. Простота функции, степень дублирования, избегание -decltype
все это может способствовать этому, но ни один из них не будет надежно решающим.auto
в целом.auto
ключевое слово, отобразится фактический тип возвращаемого значения.int*
, но на самом деле важно то, что причина вint*
том, чтоstd::vector<int>::iterator_type
это то, что есть в ваших текущих параметрах сборки!Вообще говоря, тип возвращаемого значения функции очень помогает документировать функцию. Пользователь будет знать, что ожидается. Однако есть один случай, когда я думаю, было бы неплохо отказаться от этого возвращаемого типа, чтобы избежать избыточности. Вот пример:
template<typename F, typename Tuple, int... I> auto apply_(F&& f, Tuple&& args, int_seq<I...>) -> decltype(std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...)) { return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...); } template<typename F, typename Tuple, typename Indices = make_int_seq<std::tuple_size<Tuple>::value>> auto apply(F&& f, Tuple&& args) -> decltype(apply_(std::forward<F>(f), std::forward<Tuple>(args), Indices())) { return apply_(std::forward<F>(f), std::forward<Tuple>(args), Indices()); }
Этот пример взят из официального документа комитета N3493 . Назначение функции
apply
- переадресовать элементы astd::tuple
функции и вернуть результат.int_seq
иmake_int_seq
являются лишь частью реализации, и, вероятно , только запутать любой пользователь , пытаясь понять , что он делает.Как видите, возвращаемый тип - это не что иное,
decltype
как возвращаемое выражение. Более того,apply_
поскольку он не предназначен для того, чтобы его видели пользователи, я не уверен в полезности документирования его возвращаемого типа, когда он более или менее совпадаетapply
с его типом . Я думаю, что в данном конкретном случае отказ от возвращаемого типа сделает функцию более читаемой. Обратите внимание, что этот самый возвращаемый тип фактически был исключен и заменен на N3915decltype(auto)
в предложении о добавленииapply
к стандарту (также обратите внимание, что мой исходный ответ предшествует этой статье):template <typename F, typename Tuple, size_t... I> decltype(auto) apply_impl(F&& f, Tuple&& t, index_sequence<I...>) { return forward<F>(f)(get<I>(forward<Tuple>(t))...); } template <typename F, typename Tuple> decltype(auto) apply(F&& f, Tuple&& t) { using Indices = make_index_sequence<tuple_size<decay_t<Tuple>>::value>; return apply_impl(forward<F>(f), forward<Tuple>(t), Indices{}); }
Однако в большинстве случаев лучше сохранить этот возвращаемый тип. В конкретном случае, который я описал выше, тип возвращаемого значения довольно нечитабелен, и потенциальный пользователь ничего не получит от его знания. Намного полезнее будет хорошая документация с примерами.
Еще одна вещь, о которой еще не упоминалось: while
declype(t+u)
позволяет использовать выражение SFINAE ,decltype(auto)
но не позволяет (хотя есть предложение изменить это поведение). Возьмем, к примеру,foobar
функцию, которая будет вызывать функцию-foo
член типа, если она существует, или вызыватьbar
функцию-член типа, если она существует, и предполагать, что класс всегда имеет точное значениеfoo
илиbar
ни то, ни другое сразу:struct X { void foo() const { std::cout << "foo\n"; } }; struct Y { void bar() const { std::cout << "bar\n"; } }; template<typename C> auto foobar(const C& c) -> decltype(c.foo()) { return c.foo(); } template<typename C> auto foobar(const C& c) -> decltype(c.bar()) { return c.bar(); }
Вызов
foobar
экземпляраX
будет отображаться,foo
а вызовfoobar
экземпляраY
будет отображатьсяbar
. Если вместо этого вы используете автоматическое определение типа возвращаемого значения (с или безdecltype(auto)
), вы не получите выражение SFINAE и вызовfoobar
экземпляра любого из нихX
илиY
вызовет ошибку времени компиляции.источник
В этом нет необходимости. Что касается того, когда вам следует - вы получите много разных ответов по этому поводу. Я бы сказал, что нет, до тех пор, пока он не станет действительно принятой частью стандарта и таким же образом будет хорошо поддерживаться большинством основных компиляторов.
Кроме того, это будет религиозный аргумент. Я бы лично сказал, что никогда не указывать фактический тип возвращаемого значения делает код более понятным, намного проще в обслуживании (я могу посмотреть на сигнатуру функции и узнать, что она возвращает, а не на самом деле читать код), и это устраняет возможность того, что вы думаете, что он должен возвращать один тип, а компилятор считает, что другой вызывает проблемы (как это случалось со всеми языками сценариев, которые я когда-либо использовал). Я думаю, что авто было гигантской ошибкой и причинит на порядки больше боли, чем помощи. Другие скажут, что вы должны использовать его постоянно, так как это соответствует их философии программирования. В любом случае, это выходит за рамки этого сайта.
источник
#define
s для превращения C ++ в VB. Функции обычно имеют хороший консенсус в мировоззрении языка о том, что правильно, а что нет, в зависимости от того, к чему привыкли программисты этого языка. Одна и та же функция может присутствовать на нескольких языках, но для каждого из них существуют свои правила ее использования.auto
это чистым благословением. Это убирает много избыточности. Иногда просто неудобно повторять возвращаемый тип. Если вы хотите вернуть лямбду, это может быть даже невозможно без сохранения результата в astd::function
, что может повлечь за собой некоторые накладные расходы.auto
чтобы он всегда соответствовал типу возвращаемого значения переданной функции.auto
для конечного возвращаемого типа.]Это не имеет ничего общего с простотой функции (как предполагается, теперь удаленная копия этого вопроса).
Либо тип возврата является фиксированным (не использовать
auto
), либо сложным образом зависит от параметра шаблона (auto
в большинстве случаев используется в паре с,decltype
когда есть несколько точек возврата).источник
Рассмотрим реальную производственную среду: многие функции и модульные тесты взаимозависимы от типа возвращаемого значения
foo()
. Теперь предположим, что тип возвращаемого значения необходимо изменить по какой-либо причине.Если тип возвращаемого значения присутствует
auto
везде, а вызывающиеfoo()
и связанные функции используютauto
при получении возвращаемого значения, изменения, которые необходимо внести, минимальны. В противном случае это может означать часы чрезвычайно утомительной и подверженной ошибкам работы.В качестве реального примера меня попросили изменить модуль с использования повсюду необработанных указателей на интеллектуальные указатели. Исправление модульных тестов было более болезненным, чем сам код.
Хотя есть и другие способы решения этой проблемы, использование
auto
возвращаемых типов кажется подходящим вариантом.источник
decltype(foo())
?typedef
s и alias (то естьusing
объявление типа) лучше в этих случаях, особенно когда они привязаны к классу.Я хочу привести пример, в котором тип возврата auto идеален:
Представьте, что вы хотите создать короткий псевдоним для длительного последующего вызова функции. При использовании auto вам не нужно заботиться об исходном типе возврата (возможно, в будущем он изменится), и пользователь может щелкнуть исходную функцию, чтобы получить реальный тип возврата:
inline auto CreateEntity() { return GetContext()->GetEntityManager()->CreateEntity(); }
PS: Зависит от этого вопроса.
источник
Для сценария 3 я бы повернул возвращаемый тип сигнатуры функции с возвращаемой локальной переменной. Это сделало бы для программистов-клиентов более ясным, что функция возвращает. Как это:
Сценарий 3 Чтобы предотвратить избыточность:
std::vector<std::map<std::pair<int, double>, int>> foo() { decltype(foo()) ret; return ret; }
Да, у него нет ключевого слова auto, но принцип тот же, чтобы предотвратить избыточность и облегчить программистам, у которых нет доступа к исходному тексту.
источник
vector<map<pair<int,double>,int>
а затем использовать это.