Пропустить возвращаемый тип в C ++ 11 - PullRequest
41 голосов
/ 24 декабря 2010

Я недавно обнаружил, что использую следующий макрос с gcc 4.5 в режиме C ++ 11:

#define RETURN(x) -> decltype(x) { return x; }

И пишу такие функции:

template <class T>
auto f(T&& x) RETURN (( g(h(std::forward<T>(x))) ))

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

Проблемаявляется то, что этот метод работает только на одной линии функций.Поэтому, когда у меня есть что-то вроде этого (замысловатый пример):

template <class T>
auto f(T&& x) -> ...
{
   auto y1 = f(x);
   auto y2 = h(y1, g1(x));
   auto y3 = h(y1, g2(x));
   if (y1) { ++y3; }
   return h2(y2, y3);
}

Затем я должен добавить что-то ужасное в тип возвращаемого значения.

Кроме того, всякий раз, когда я обновляю функцию, я будуМне нужно изменить тип возвращаемого значения, и если я не изменю его правильно, я получу ошибку компиляции, если мне повезет, или ошибку времени выполнения в худшем случае.Необходимость копировать и вставлять изменения в двух местах и ​​поддерживать их синхронизацию, я считаю, не является хорошей практикой.

И я не могу вспомнить ситуацию, когда я хотел бы получить неявное приведение по возвращению вместо явногоcast.

Конечно, есть способ попросить компилятор вывести эту информацию.Какой смысл компилятору хранить это в секрете?Я думал, что C ++ 11 спроектирован так, что такое дублирование не потребуется.

Ответы [ 7 ]

23 голосов
/ 26 марта 2012

Похоже, что g ++ 4.8 получает реализацию автоматического вывода типа возврата. Патч был выпущен Джейсоном Мерриллом, который также отправляет статью для C ++ - 1Y для этой функции. Функция доступна с -std = c ++ 1y.

Все еще играю с ним.

11 голосов
/ 01 января 2011

Обоснование этого поведения приведено в черновике 8.3.5p12:

Тип трейлинг-возврата наиболее полезен для типа, который был бы более сложно определить до описатель-ID:

template <class T, class U> auto add(T t, U u) -> decltype(t + u);

вместо

template <class T, class U> decltype((*(T*)0) + (*(U*)0)) add(T t, U u);

Таким образом, это действительно только для упрощения случая, когда помогает ссылка на имена параметров.

Если вы предполагаете, что C ++ может всегда выводить тип возврата функций из тела функции: это не сработает. Цель C ++ (и C) - позволить модульность, отделяя объявление от реализации, поэтому в момент вызова у вас может не быть доступного тела функции. Однако каждый вызывающий должен знать типы параметров и тип возврата каждой вызываемой функции / метода.

7 голосов
/ 28 декабря 2010

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

template <class R = int, class T>
R f(T&& x)
{
   ...
   return h2(y2, y3);
}

Код ниже демонстрирует его эффективность.

ДЕМО-КОД:

#include <iostream>
#include <iomanip>

template <class T, class S>
T h2(T& x, S& y)
{
  return x + y;
}

template <class R = int, class T>
R f(T& x)
{
  auto y2 = x;
  auto y3 = x;
  return h2(y2, y3);
}

int main(int argc, char** argv)
{
  int x = 7;
  std::string str = "test! ";

  auto d = f<double>(x);
  auto i = f(x); // use default type (int)
  auto s = f<std::string>(str);

  std::cout << std::fixed << std::setprecision(4);
  std::cout << "double: " << d << std::endl;
  std::cout << "int: " << i << std::endl;
  std::cout << "string: " << s << std::endl;

  return 0;
}

ВЫВОД:

double: 14.0000
int: 14
string: test! test!

К сожалению, нужной вам функциональности не существует (пока) и она не является частью спецификации C ++ 0x. Однако, возможно, это может быть частью спецификации C ++ 1x, когда она разрабатывается. до тех пор придерживайтесь шаблонов.

6 голосов
/ 01 января 2011

РЕДАКТИРОВАТЬ: упс, я только что понял, что есть разница между определителем trailing-return-type и оператором return.В частности:

auto f(int a)
{
    char r[sizeof(f(a))+1];
    return r;
}

Kaboom!


Предыдущий ответ:

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

В частности, речь идет о случае, когда внутри функции находится ровно один оператор возврата.

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

return (ugly expression);

в

auto return_value = (ugly expression);
return return_value;

Если компилятор может вывести типиз return_value (и в соответствии с правилами C ++ 0x, это возможно), тогда предполагаемый тип return_value может быть выбран в качестве возвращаемого типа функции.

Поэтому мне кажется, чтомодификация C ++ 0x, где спецификатор завершающего типа возврата должен требоваться только тогда, когда множественность операторов возврата не совсем одна, будет выполнимо и решитпроблема.

3 голосов
/ 05 октября 2011

Я согласен с Yttrill.Вывод типа возврата уже доказал свою практичность в таких языках, как Haskell, и, поскольку C ++ уже достиг «авто», это всего лишь еще один шаг к достижению вывода типа возврата.Этот вывод должен происходить во время специализации, а не определения шаблона, поскольку необходима информация о реальном типе, предоставляемая шаблону.Разделение объявлений и определений больше не является обычной практикой в ​​универсальном C ++, потому что тело шаблона должно быть записано в заголовочных файлах, и, следовательно, тела шаблона почти всегда идут с объявлениями шаблона.В ситуациях, когда существует несколько операторов возврата и их типы не совпадают, компилятор может с радостью сообщить об ошибке.Таким образом, выведение возвращаемого типа полностью возможно в C ++, если этого пожелает комитет.И это ОЧЕНЬ важно, потому что дублирование написанных вручную типов возврата препятствует повсеместному использованию небольших универсальных вспомогательных функций, что является такой распространенной практикой в ​​функциональном и универсальном программировании.

2 голосов
/ 31 декабря 2010

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

Я удивлен, что "auto" не работает для типов возврата, это было определенно на табличке. Было ли это слишком сложно реализовать? [Это не тривиально, учитывая, что функция может быть рекурсивной].

Оооо ... теперь я вижу. Проблема заключается в глупости шаблона оформления функции "зависимые имена":

template<class T> auto f(T a) { return a.f(); }

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

0 голосов
/ 26 декабря 2010

Ваш тип возврата действительно часто меняется?Почему вы не можете просто указать это явно?Пример:

template<class T>
int f(T&& x)
{
...
}

Люди занимались этим более двадцати лет ...

...