Законно ли в современном C++ определять возвращаемую переменную в объявлении функции?

Я нашел странный кусок грамматики C++ в CodeSignal:

string r, longestDigitsPrefix(string s)
{
   for(auto const c : s)
   {
      if(isdigit(c))
        r += c;
      else
        break;
   }
   return r;
}

Первая строка определяет string r перед объявлением функции. Это действительно в современном C++?

Приведенный выше код компилирует и проходит все тесты в консоли CodeSignal, но он выдает ошибку компилятора, когда я пытался скомпилировать локально (--std=C++14).

Это действительная грамматика в современном C++? Если да, то какой стандартной редакции он соответствует?

Ответ 1

Да, грамматика C++ странная. В основном, когда дело доходит до объявлений (и только объявлений), у нас есть такая вещь, где:

T D1, D2, ... ,Dn;

означает ([dcl.dcl]/3):

T D1;
T D2;
...
T Dn;

Это будет знакомо в обычных случаях:

int a, b; // declares two ints

И, вероятно, в тех случаях, о которых вам сказали беспокоиться:

int* a, b, *c; // a and c are pointers to int, b is just an int

Но деклараторы могут вводить и другие вещи:

int *a, b[10], (*c)[10], d(int);

Здесь a - указатель на int, b - массив из 10 int s, c - указатель на массив из 10 int s, а d - функция, принимающая int возвращающая int.


Однако это относится только к декларациям. Итак, это:

string r, longestDigitsPrefix(string s);

является действительным объявлением C++, в котором r является string а longestDigitsPrefix - функцией, принимающей string и возвращающей string.

Но это:

string r, longestDigitsPrefix(string s) { return s; }

недействителен C++. Определения функций имеют свою собственную грамматику и не могут отображаться как часть списка инициализации объявления.

Определение этой функции также неверно, так как она использует глобальную переменную для отслеживания состояния. Таким образом, даже если бы это было допустимо, longestDigitsPrefix("12c") вернул бы "12" в первый раз, но "1212" во второй раз...

Ответ 2

Читая черновик ISO A C++ 14 N4140 Приложение A [грамм], я почти уверен, что это неверно, так как я не могу найти способ вывести грамматику из единицы перевода из

единица перевода → объявление-последовательность → объявление → объявление блока | определение функции | спецификация связи |...

определение-функции: атрибут-спецификатор-seqopt decl-спецификатор-seqopt декларатор virt-specier-seqopt тело-функции

декларатор: ptr-декларатор noptr-декларатор параметры и квалификаторы конечный тип возврата

Но ваша строка - это скорее оператор запятой, а ее грамматика:

выражение: присваивание-выражение | выражение, присваивание-выражение

выражение-присваивание: выражение-условие | логическое или выражение | оператор присваивания | инициализатор-предложение | вбрасывание выражение

И нет пути от assignment-expression до function-definition

Обновление: благодаря Барри, еще один способ попытаться проанализировать ваш текст, попробуйте перейти из init-declarator-list (который вы можете получить из block-declaration) к определению function-definition:

список инициаторов объявления: инициатор объявлений | список инициаторов, список инициаторов

init-декларатор: декларатор initializeropt

А также

декларатор: ptr-декларатор noptr-декларатор параметры и квалификаторы конечный тип возврата

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

#include <string>
using std::string;

string r, longestDigitsPrefix(string s);

string longestDigitsPrefix(string s) {
    for(auto const c : s)
    {
        if(isdigit(c))
            r += c;
        else
            break;
    }
    return r;
}

int main(int argc, char *argv[]) {
    longestDigitsPrefix("foo");

    return 0;
}

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

Ответ 3

Представленное вами объявление эквивалентно этой паре объявлений:

string r;
string longestDigitsPrefix(string s) 
{
   for(auto const c : s)
   {
      if(isdigit(c)) r += c;
      else break;
   }
   return r;
}

Это стало возможным и всегда было возможно благодаря правилам, допускающим использование нескольких деклараторов в одной декларации. Так же, как следующее:

int a, *b;

Который вводит две переменные другого типа в одном объявлении.

Ответ 4

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

Тем не менее, выражение

string r, longestDigitsPrefix(string s) { /* anything */ }

является недействительным. Он недопустим, потому что он не соответствует грамматике языка C++.

Мы всегда можем использовать макрос для объявления указателя на функцию с присваиванием с объявлением лямбда-функции, так что будет принят braced-init-list. Но вам нужно будет добавить ; в конце, так как в любом случае указание init-декларации. Но с макросами все возможно.

#define longestDigitsPrefix(x) (*longestDigitsPrefix)(string) = [](x) -> string
string r, longestDigitsPrefix(string s)
{
   for(auto const c : s)
   {
      if(isdigit(c))
        r += c;
      else
        break;
   }
   return r;
};

Ответ 5

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

Мой g++ (GCC) 8.2.1 позволяет это делать в классе. Это скомпилируется без предупреждения (-Wall) и вернет 42.

struct Weird {
    int r, getR() {return r;};
};

int main() {
    Weird w={42};
    return w.getR();
}

Ответ 6

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

Ответ 7

Насколько мне известно, это недопустимое объявление функции.

Префикс в функциях может использоваться только как способ определить, какой тип является функцией, "возвращающей" переменную, а не самой переменной.

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

пример

std::string foo = "Hello";

void foo_func(std::string &string)
{ //do something with the string
}

//call the function and modify the global "foo" string
foo_func(foo);

Ответ 8

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

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