С выпуском GCC 4.8.0 у нас есть компилятор, который поддерживает автоматическое вычитание типа возврата, часть C ++ 14. С -std=c++1y
, Я могу сделать это:
auto foo() { //deduced to be int
return 5;
}
Мой вопрос: когда я должен использовать эту функцию? Когда это необходимо и когда это делает код чище?
Первый сценарий, о котором я могу подумать, — это всегда, когда это возможно. Каждая функция, которая может быть написана таким образом, должна быть. Проблема в том, что это не всегда делает код более читабельным.
Следующий сценарий — избегать более сложных типов возвращаемых данных. Как очень легкий пример:
template<typename T, typename U>
auto add(T t, U u) { //almost deduced as decltype(t + u): decltype(auto) would
return t + u;
}
Я не верю, что это когда-либо действительно будет проблемой, хотя я предполагаю, что наличие возвращаемого типа, явно зависящего от параметров, может быть более ясным в некоторых случаях.
Далее, чтобы предотвратить избыточность:
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};
вместо вектора, но это не всегда работает, и нам нужно указать тип как в заголовке функции, так и в теле функции. Это просто избыточно, и автоматическое вычитание типа возврата избавляет нас от этой избыточности.
Наконец, его можно использовать вместо очень простых функций:
auto position() {
return pos_;
}
auto area() {
return length_ * width_;
}
Иногда, однако, мы можем посмотреть на функцию, желая узнать точный тип, и если она там не указана, мы должны перейти к другой точке кода, например, где pos_
объявлен
С этими сценариями, какие из них на самом деле оказываются в ситуации, когда эта функция полезна для того, чтобы сделать код чище? Как насчет сценариев, о которых я не упомянул? Какие меры предосторожности следует предпринять, прежде чем использовать эту функцию, чтобы она не укусила меня позже? Есть ли что-то новое, что эта функция приносит на стол, что невозможно без нее?
Обратите внимание, что несколько вопросов предназначены для помощи в поиске перспектив, с которых можно ответить на этот вопрос.
C ++ 11 поднимает похожие вопросы: когда использовать вывод типа возврата в лямбдах, а когда использовать auto
переменные.
Традиционный ответ на вопрос в C и C ++ 03 был «через границы операторов мы делаем типы явными, в выражениях они обычно неявные, но мы можем сделать их явными с помощью приведений». C ++ 11 и C ++ 1y вводят инструменты вывода типов, чтобы вы могли опускать тип в новых местах.
Извините, но вы не собираетесь решать это заранее, устанавливая общие правила. Вам нужно посмотреть на конкретный код и решить для себя, будет ли он способствовать удобочитаемости, чтобы указывать типы повсюду: лучше ли в вашем коде сказать «тип этой вещи X» или лучше Ваш код говорит: «тип этой вещи не имеет отношения к пониманию этой части кода: компилятор должен знать, и мы могли бы, вероятно, решить это, но нам не нужно говорить это здесь»?
Поскольку «удобочитаемость» не является объективно определенной [*] и, кроме того, она зависит от читателя, вы несете ответственность как автор / редактор фрагмента кода, который не может быть полностью удовлетворен руководством по стилю. Даже в том случае, если в руководстве по стилю действительно указаны нормы, разные люди предпочтут разные нормы и будут склонны находить что-то незнакомое для «менее читабельного». Таким образом, удобочитаемость конкретного предложенного правила стиля часто может оцениваться только в контексте других действующих правил стиля.
Все ваши сценарии (даже первые) найдут применение для чьего-либо стиля кодирования. Лично я считаю второй вариант наиболее привлекательным, но даже в этом случае я ожидаю, что он будет зависеть от ваших инструментов документирования. Не очень полезно видеть документально подтвержденное, что тип возвращаемого значения шаблона функции auto
тогда как видя это задокументировано как decltype(t+u)
создает опубликованный интерфейс, на который можно (надеюсь) положиться.
Вообще говоря, тип возвращаемого значения функции очень полезен для документирования функции. Пользователь будет знать, что ожидается. Однако есть один случай, когда я думаю, что было бы неплохо отбросить этот тип возвращаемого значения, чтобы избежать избыточности. Вот пример:
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
это переслать элементы std::tuple
к функции и вернуть результат. int_seq
а также make_int_seq
являются лишь частью реализации, и, вероятно, только запутают любого пользователя, пытающегося понять, что он делает.
Как видите, тип возвращаемого значения — не более чем decltype
возвращенного выражения. Более того, apply_
Я не уверен в полезности документирования возвращаемого типа, когда он более или менее совпадает с apply
один. Я думаю, что в данном конкретном случае удаление возвращаемого типа делает функцию более читабельной. Обратите внимание, что этот тип возвращаемого значения фактически был удален и заменен decltype(auto)
в предложении добавить apply
к стандарту, N3915 (также обратите внимание, что мой оригинальный ответ предшествует этой статье):
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{});
}
Тем не менее, в большинстве случаев лучше сохранять этот тип возвращаемого значения. В конкретном случае, который я описал выше, возвращаемый тип довольно нечитабелен, и потенциальный пользователь ничего не узнает об этом. Хорошая документация с примерами будет гораздо полезнее.
Еще одна вещь, которая еще не упоминалась: пока declype(t+u)
позволяет использовать выражение сфина, 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
вызовет ошибку во время компиляции.
Это никогда не нужно. Что касается того, когда вы должны — вы получите много разных ответов об этом. Я бы сказал, что совсем нет, пока не станет принятой частью стандарта и не будет поддерживаться большинством основных компиляторов таким же образом.
Помимо этого, это будет религиозный аргумент. Я бы лично сказал, что никогда не вводить фактический тип возвращаемого значения делает код более понятным, его намного легче обслуживать (я могу посмотреть на сигнатуру функции и узнать, что она возвращает против фактического чтения кода), и это исключает возможность того, что вы думаете, что он должен возвращать один тип, а компилятор думает, что другой вызывает проблемы (как это было с каждым языком сценариев, который я когда-либо использовал). Я думаю, что авто было гигантской ошибкой, и оно будет причинять боль больший ущерб, чем помощь. Другие скажут, что вы должны использовать это все время, поскольку это соответствует их философии программирования. В любом случае, это выход за рамки этого сайта.
Это не имеет ничего общего с простотой функции (как удаленный дубликат этого вопроса предполагается).
Либо возвращаемый тип является фиксированным (не используйте auto
) или сложным образом зависит от параметра шаблона (используйте auto
в большинстве случаев в паре с decltype
когда есть несколько точек возврата).
Рассмотрим реальную производственную среду: многие функции и модульные тесты все зависят от типа возвращаемого значения. foo()
, Теперь предположим, что тип возвращаемого значения должен быть изменен по любой причине.
Если тип возврата auto
везде и звонящие foo()
и использование связанных функций auto
при получении возвращаемого значения изменения, которые необходимо внести, минимальны. Если нет, это может означать часы чрезвычайно утомительной и подверженной ошибкам работы.
В качестве примера из реальной жизни меня попросили изменить модуль с использования сырых указателей повсюду на интеллектуальные указатели. Исправление юнит-тестов было более болезненным, чем реальный код.
Хотя есть и другие способы, которыми можно справиться, использование auto
возвращаемые типы кажутся подходящими.
Я хочу привести пример, когда тип возвращаемого значения 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, но основной принцип тот же, что предотвращает избыточность и облегчает программистам, не имеющим доступа к источнику.