【问题标题】:Is it legitimate in modern C++ to define a return variable in the function declaration?在现代 C++ 中在函数声明中定义返回变量是否合法?
【发布时间】:2019-06-14 02:24:11
【问题描述】:

我在CodeSignal上发现了一段奇怪的C++语法:

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++ 中的有效语法吗?如果是,它符合哪个标准修订版?

【问题讨论】:

  • 你在哪里找到这个以及哪个编译器接受这个?从未见过这种(无效的)语法。
  • @MatthieuBrucher - 仔细检查后,语法产生的不同分支。
  • @MatthieuBrucher - 无效。我正在查看它描述的语法生成 decl a; decl b; - ab 可以是函数定义(注意分号)。但是函数定义可能不会出现在decl a, b;
  • string r, longestDigitsPrefix(string s); 将是有效的。您发布的代码无效。
  • 你真的应该发布这个的 MCVE 版本,一个你自己用你的编译器使用的版本。

标签: c++ language-lawyer declaration


【解决方案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个ints组成的数组,c是一个指向一个由10个ints组成的数组的指针,d是一个函数一个int 返回一个int


但是,这适用于声明。所以这个:

string r, longestDigitsPrefix(string s);

是一个有效的 C++ 声明,它将 r 声明为 string,将 longestDigitsPrefix 声明为接受 string 并返回 string 的函数。

但是这个:

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

是无效的 C++。函数定义有自己的语法,不能作为 init-declarator-list 的一部分出现。

该函数的定义也很糟糕,因为它使用全局变量来跟踪状态。所以即使它是有效的,longestDigitsPrefix("12c") 第一次会返回"12",但第二次会返回"1212"...

【讨论】:

  • 我觉得你的回答不完整,也可以考虑一下“内置逗号运算符 逗号运算符表达式的形式为 E1 , E2 在逗号表达式 E1, E2 中,表达式 E1 是评估,其结果被丢弃“en.cppreference.com/w/cpp/language/operator_other
  • 逗号运算符可以用作表达式的一部分,但这里提出的代码在语法上不是表达式(它是一个语句) ,所以我认为没有必要查看逗号运算符来反驳这在语法上是正确的。
  • @templatetypedef 很难对语法错误的陈述进行推理,我试图涵盖在这种情况下看到逗号时可以想到的所有情况,但我明白你的意思。
  • @templatetypedef:这不是声明;这些只能出现在语句块内。
  • @Ben Voigt 我的立场是正确的!那么这个语法是什么?只是一个纯粹的声明?
【解决方案2】:

通过阅读 ISO C++14 草案 N4140 附件 A [gram],我很确定它是不正确的,因为我找不到从

的翻译单元推断语法的方法

翻译单元 -> 声明序列 -> 声明 -> 块声明 |函数定义 |链接规范 | ...

函数定义:属性说明符-seqopt decl-说明符-seqopt 声明符 virt-specifier-seqopt 函数体

declarator: ptr-declarator noptr-declarator parameters-and-qualifiers 尾随返回类型

但你的行更多的是逗号运算符,但它的语法是:

表达式: 赋值表达式 |表达式,赋值表达式

赋值表达式:条件表达式 |逻辑或表达式 | 赋值运算符 |初始化子句 |抛出表达式

而且没有从assignment-expressionfunction-definition的途径

更新: 感谢 Barry,另一种尝试自下而上解析文本的方法是尝试从 init-declarator-list(您可以从 block-declaration 获取)到 function-definition

初始化声明符列表:初始化声明符 |初始化声明器列表, 初始化声明器

init-declarator: 声明器初始化选择

还有

declarator: ptr-declarator noptr-declarator parameters-and-qualifiers 尾随返回类型

允许您声明函数但不允许定义。所以这个奇怪的代码是合法的:

#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】:

    据我所知,这不是一个有效的函数声明。

    函数中的前缀只能用来定义函数的“返回”变量是什么类型,而不是变量本身。

    但是,您可以在函数外部声明一个变量(从而使其成为全局变量)并在函数内部对其进行修改,正如您可能已经尝试过的那样,但是您需要将所述变量作为引用传递给函数的参数。您还需要将您的函数设置为 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);
    

    【讨论】:

    • 由于问题带有language-lawyer 标签,我们希望得到带有 C++ 标准引号的答案。
    【解决方案4】:

    如果你这样做是为了优化,那么只要只有一个变量被返回,那么大多数编译器都会将返回变量分配在调用者的堆栈上,而不是被调用者的堆栈上。

    您所要做的就是在被调用者的上下文中声明变量,并确保有 1 个返回点,或者所有返回点都返回相同的变量。

    【讨论】:

    • 由于问题带有language-lawyer 标签,我们希望得到带有 C++ 标准引号的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多