为什么通过地址传递大括号初始化临时需要在MSVS中显式转换为相同类型

sun*_*oon 3 c++ casting visual-c++ c++11 list-initialization

我试图通过替换两个不同的双线程来处理Windows API时使代码变得不那么臃肿

TEMP t{0,1,2}; // let's say it's struct TEMP {int a; int b; int c}
SomeVeryVerboseFunctionName(&t);
Run Code Online (Sandbox Code Playgroud)

与单行

SomeVeryVerboseFunctionName(&TEMP{0,1,2});
Run Code Online (Sandbox Code Playgroud)

但偶然发现错误:

expression必须是左值或函数指示符.

经过多次尝试,我终于想出了编译的代码(MSVS 2013u4):

SomeVeryVerboseFunctionName(&(TEMP) TEMP{0,1,2});//explicit cast to the same type!
Run Code Online (Sandbox Code Playgroud)

为了更好地理解为什么需要演员,我设置了一个简单的测试项目:

#include <stdio.h>

struct A
{
    int a;
    int b;
    A(int _a, int _b) : a(_a), b(_b) {};
};

struct B
{
    int a;
    int b;
};

template <typename T> void fn(T* in)
{
    printf("a = %i, b = %i\n", in->a, in->b);
}

int main()
{
    fn(&A{ 1, 2 });      //OK, no extra magick
    /*  fn(&B {3, 4});      //error: expression must be an lvalue or function designator */
    fn(&(B)B{ 3, 4 });  //OK with explicit cast to B (but why?)
}
Run Code Online (Sandbox Code Playgroud)

并发现如果某个struct T 有一个显式构造函数(如A上面的代码中的has),则可以获取类型为brace-initialized的临时地址T并将其传递给带有指针的函数T*,但如果不是有一个(像B),然后出现所述错误,只能通过显式转换来键入T.

所以问题是:为什么B需要这种奇怪的铸造而A不是?

更新

现在很明显,将左值作为左值处理是MSVS中的扩展/特征/错误,是否有人担心假装它实际上是一个特征(足以用于MS自2010年以来维护它)并详细说明为什么临时AB需要通过它以不同的方式来满足编译器?它必须与A的构造函数有关,B缺乏它...

Geo*_*ard 12

你在做什么在C++中实际上是非法的.

Clang 3.5抱怨:

23 : error: taking the address of a temporary object of type 'A' [-Waddress-of-temporary]
fn(&A {1, 2}); //OK, no extra magick
   ^~~~~~~~~

25 : error: taking the address of a temporary object of type 'B' [-Waddress-of-temporary]
fn(&(B) B {3, 4}); //OK with explicit cast to B (but why?)
   ^~~~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)

所有操作数&必须是左值,而不是临时值.MSVC接受这些结构的事实是一个错误.根据Shafik上面指出的链接,似乎MSVC错误地为这些创造了左值.

  • 我喜欢他们如何"修复"它并且正确的行为隐藏在[flag](https://msdn.microsoft.com/en-us/library/dn449507.aspx)后面以避免破坏遗留代码. (4认同)
  • [直接链接到有问题的bug](https://connect.microsoft.com/VisualStudio/Feedback/Details/615622) (2认同)

Yak*_*ont 5

template<class T>
T& as_lvalue(T&& t){return t;}
// optional, blocks you being able to call as_lvalue on an lvalue:
template<class T>
void as_lvalue(T&)=delete;
Run Code Online (Sandbox Code Playgroud)

将使用合法的C++解决您的问题.

SomeVeryVerboseFunctionName(&as_lvalue(TEMP{0,1,2}));
Run Code Online (Sandbox Code Playgroud)

从某种意义上说,as_lvalue是一个反向的move.你可以打电话给它unmove,但这会让人感到困惑.

在C++中取rvalue的地址是非法的.以上将rvalue变为左值,此时地址变为合法.

取rvalue的地址是非法的原因是这些数据意味着被丢弃.指针仅在当前行结束时保持有效(禁止通过左值的转换创建rvalue).这样的指针只有角落有用的东西.但是,对于Windows API,许多此类API都会指向数据结构以进行C样式版本控制.

为此,这些可能更安全:

template<class T>
T const& as_lvalue(T&& t){return t;}
template<class T>
T& as_mutable_lvalue(T&& t){return t;}
// optional, blocks you being able to call as_lvalue on an lvalue:
template<class T>
void as_lvalue(T&)=delete;
template<class T>
void as_mutable_lvalue(T&)=delete;
Run Code Online (Sandbox Code Playgroud)

因为更可能是正确的,所以返回const对数据的引用(为什么要修改临时?),较长的(因此不太可能使用)返回非const版本.

MSVC有一个旧的"bug"/"特性",它将许多东西当作左值处理,当它不应该时,包括演员表的结果.使用/Za禁用该扩展.这可能导致其他工作代码无法编译.它甚至可能导致工作代码无法工作并仍然编译:我没有证明相反.