Когда я должен использовать автоматическое вычитание типа возврата C ++ 14?

С выпуском 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_ объявлен

Заключение

С этими сценариями, какие из них на самом деле оказываются в ситуации, когда эта функция полезна для того, чтобы сделать код чище? Как насчет сценариев, о которых я не упомянул? Какие меры предосторожности следует предпринять, прежде чем использовать эту функцию, чтобы она не укусила меня позже? Есть ли что-то новое, что эта функция приносит на стол, что невозможно без нее?

Обратите внимание, что несколько вопросов предназначены для помощи в поиске перспектив, с которых можно ответить на этот вопрос.

119

Решение

C ++ 11 поднимает похожие вопросы: когда использовать вывод типа возврата в лямбдах, а когда использовать auto переменные.

Традиционный ответ на вопрос в C и C ++ 03 был «через границы операторов мы делаем типы явными, в выражениях они обычно неявные, но мы можем сделать их явными с помощью приведений». C ++ 11 и C ++ 1y вводят инструменты вывода типов, чтобы вы могли опускать тип в новых местах.

Извините, но вы не собираетесь решать это заранее, устанавливая общие правила. Вам нужно посмотреть на конкретный код и решить для себя, будет ли он способствовать удобочитаемости, чтобы указывать типы повсюду: лучше ли в вашем коде сказать «тип этой вещи X» или лучше Ваш код говорит: «тип этой вещи не имеет отношения к пониманию этой части кода: компилятор должен знать, и мы могли бы, вероятно, решить это, но нам не нужно говорить это здесь»?

Поскольку «удобочитаемость» не является объективно определенной [*] и, кроме того, она зависит от читателя, вы несете ответственность как автор / редактор фрагмента кода, который не может быть полностью удовлетворен руководством по стилю. Даже в том случае, если в руководстве по стилю действительно указаны нормы, разные люди предпочтут разные нормы и будут склонны находить что-то незнакомое для «менее читабельного». Таким образом, удобочитаемость конкретного предложенного правила стиля часто может оцениваться только в контексте других действующих правил стиля.

Все ваши сценарии (даже первые) найдут применение для чьего-либо стиля кодирования. Лично я считаю второй вариант наиболее привлекательным, но даже в этом случае я ожидаю, что он будет зависеть от ваших инструментов документирования. Не очень полезно видеть документально подтвержденное, что тип возвращаемого значения шаблона функции autoтогда как видя это задокументировано как decltype(t+u) создает опубликованный интерфейс, на который можно (надеюсь) положиться.

[*] Иногда кто-то пытается сделать объективные измерения. В той степени, в которой кто-либо когда-либо придумывает какие-либо статистически значимые и общеприменимые результаты, работающие программисты полностью игнорируют их в пользу авторских инстинктов того, что является «читабельным».

53

Другие решения

Вообще говоря, тип возвращаемого значения функции очень полезен для документирования функции. Пользователь будет знать, что ожидается. Однако есть один случай, когда я думаю, что было бы неплохо отбросить этот тип возвращаемого значения, чтобы избежать избыточности. Вот пример:

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 вызовет ошибку во время компиляции.

22

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

Помимо этого, это будет религиозный аргумент. Я бы лично сказал, что никогда не вводить фактический тип возвращаемого значения делает код более понятным, его намного легче обслуживать (я могу посмотреть на сигнатуру функции и узнать, что она возвращает против фактического чтения кода), и это исключает возможность того, что вы думаете, что он должен возвращать один тип, а компилятор думает, что другой вызывает проблемы (как это было с каждым языком сценариев, который я когда-либо использовал). Я думаю, что авто было гигантской ошибкой, и оно будет причинять боль больший ущерб, чем помощь. Другие скажут, что вы должны использовать это все время, поскольку это соответствует их философии программирования. В любом случае, это выход за рамки этого сайта.

7

Это не имеет ничего общего с простотой функции (как удаленный дубликат этого вопроса предполагается).

Либо возвращаемый тип является фиксированным (не используйте auto) или сложным образом зависит от параметра шаблона (используйте auto в большинстве случаев в паре с decltype когда есть несколько точек возврата).

7

Рассмотрим реальную производственную среду: многие функции и модульные тесты все зависят от типа возвращаемого значения. foo(), Теперь предположим, что тип возвращаемого значения должен быть изменен по любой причине.

Если тип возврата auto везде и звонящие foo() и использование связанных функций auto при получении возвращаемого значения изменения, которые необходимо внести, минимальны. Если нет, это может означать часы чрезвычайно утомительной и подверженной ошибкам работы.

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

Хотя есть и другие способы, которыми можно справиться, использование auto возвращаемые типы кажутся подходящими.

3

Я хочу привести пример, когда тип возвращаемого значения auto идеален:

Представьте, что вы хотите создать короткий псевдоним для длительного последующего вызова функции. С помощью auto вам не нужно заботиться об исходном типе возвращаемого значения (возможно, он изменится в будущем), и пользователь может щелкнуть исходную функцию, чтобы получить реальный тип возвращаемого значения:

inline auto CreateEntity() { return GetContext()->GetEntityManager()->CreateEntity(); }

PS: зависит от этот вопрос.

1

Для сценария 3 я бы перевернул возвращаемый тип сигнатуры функции с локальной переменной, которая должна быть возвращена. Для программистов-клиентов было бы понятнее, если функция возвращает.
Как это:

Сценарий 3
Для предотвращения избыточности:

std::vector<std::map<std::pair<int, double>, int>> foo() {
decltype(foo()) ret;
return ret;
}

Да, у него нет ключевого слова auto, но основной принцип тот же, что предотвращает избыточность и облегчает программистам, не имеющим доступа к источнику.

0
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector