使用临时函数对象进行全局初始化

Posted

技术标签:

【中文标题】使用临时函数对象进行全局初始化【英文标题】:Global initialization with temporary function object 【发布时间】:2013-02-06 15:03:44 【问题描述】:

以下代码

#include <random>
std::mt19937 generator((std::random_device())());

只用 clang 编译文件:

$ clang++ -c -std=c++0x test.cpp

但使用 gcc 失败:

$ g++ -c -std=c++0x test.cpp 
test.cpp:3:47: erro: expected primary-expression before ‘)’ token

该代码在 C++11 中有效吗?它是 GCC 中的错误还是 Clang 的扩展/错误?

【问题讨论】:

Fwiw,这两个都吐了(在 Intellisense 中,错误:不允许强制转换为类型“random_device()”和“)”预期表达式)仍然可以使用 VS2012 和Nov2012-CTP。 您可以使用新式构造函数语法强制构造函数调用(而不是奇怪的类型解释):std::mt19937 generator std::random_device () ; 添加另一个括号也可以:std::mt19937 generator(((std::random_device()))()); 【参考方案1】:

gcc 将子表达式 (std::random_device())() 解析为函数类型 std::random_device() 的强制转换。它有助于查看 icc 的错误输出,它比 gcc 的信息略多:

source.cpp(6): error: cast to type "std::random_device ()" is not allowed
  std::mt19937 generator((std::random_device())());
                          ^

source.cpp(6): error: expected an expression
  std::mt19937 generator((std::random_device())());
                                                ^

相关制作是5.4p2:

演员表

一元表达式 ( type-id ) cast-expression

由于一对空括号() 不是一元表达式,因此该产生式不可用,编译器应从5.2p1 中选择产生式:

后缀表达式

[...] 后缀表达式 (表达式列表opt) [...]

后缀表达式(std::random_device())表达式列表被省略。

我已经在 gcc bugzilla 上提交了http://gcc.gnu.org/bugzilla/show_bug.cgi?id=56239,看起来应该很快就会解决。

请注意,如果您向 operator() 提供参数,则 8.2p2 要求编译器将表达式解析为强制类型转换,即使强制类型转换为函数类型是非法的(如果有多个参数,则使用逗号运算符将参数列表解析为 表达式

(std::less<int>())(1, 2);
^~~~~~~~~~~~~~~~~~ illegal C-style cast
 ^~~~~~~~~~~~~~~~ type-id of function type std::less<int>()
                  ^~~~~~ argument of C-style cast
                    ^ comma operator

正确的写法(除了使用 C++11 通用初始化语法)是添加另一层括号,因为 type-id 不能包含外括号:

((std::less<int>()))(1, 2);

【讨论】:

那么,Clang 的成功是非标准行为?另外,std::random_device () 类型是什么意思? @lvella clang 是正确的;该表达式可以通过语法明确解析,因此 gcc 无法解析它是不正确的。 std::random_device() 类型是不带参数并返回 std::random_device 的函数的函数类型。 我以为是std::random_device (*)(),仅此而已。 @lvella std::random_device (*)() 是一个函数 pointer 类型。函数类型很难看到,也不经常使用,但它们是类型系统的一部分,例如函数指针类型的形式为指向 F 的指针,其中 F 是函数类型。【参考方案2】:

GCC 处理函数声明的方式似乎存在问题。举个例子:

struct A

    bool operator () ()  return true; 
;

struct B

    B(bool)  
;

B b((         // This cannot be parsed as a function declaration,
    A()()     // and yet GCC 4.7.2 interprets it as such:
    ));       // "error: 'type name' declared as function returning 
              // a function B b((A()()));"

int main()  

由于A()() 周围存在额外的括号,语法形式B b(( A()() )); 不能被解析为函数的声明。

您问题示例中的声明略有不同:

B b(
   (A())()
   );

即使在这种情况下,(A())() 也不能解释为返回函数的函数类型,该函数返回 A(总是试图将 b 视为带有未命名参数的函数声明) .所以问题是:它可以被解释为其他什么吗?如果是这样,并且如果它在这种情况下有意义,那么编译器应该考虑将整个表达式解析为对象b的构造。

这可能是 GCC 和 Clang 分歧的根本点:

int main()

    (A())(); // OK for Clang, ERROR for GCC

除了尝试构造A 类型的临时变量并调用其调用运算符之外,我看不出如何将上述内容解释为其他任何内容。它不能是函数声明,因为如果 A 被解释为返回类型,则名称丢失(反之亦然)。

另一方面,(A()) 是创建 A 类型临时的有效表达式,并且该临时支持调用运算符(其返回类型与 B 的构造函数接受的类型相同)。因此,(A())() 应该是 bool 类型的有效表达式。

由于这个原因,我认为GCC的解析是错误的。

【讨论】:

OP 写了B b ( (A())() ); @jrok:嗯,实际上你是对的。那么我是否发现了一个不相关的错误? 但是不能解析成A()是一个类型(函数返回A),所以我们调用它的默认构造函数A()() @zch 值初始化语法 T() 不允许将 T 括起来。请参阅第 5.2.3 节。

以上是关于使用临时函数对象进行全局初始化的主要内容,如果未能解决你的问题,请参考以下文章

第23课.神秘的临时对象

对象的构造与析构

类和对象进阶

JS全局变量是全局对象的属性,函数局部变量为啥就不是函数的属性呢?

C++临时对象研究

首先调用的是 DllMain() 还是全局静态对象构造函数?