【问题标题】:Why does passing a brace-initialized temporary by address require explicit casting to the same type in MSVS为什么按地址传递大括号初始化的临时地址需要在 MSVS 中显式转换为相同类型
【发布时间】:2015-07-08 15:15:35
【问题描述】:

在处理 Windows API 时,我试图通过替换两行代码来减少我的代码臃肿

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

单线

SomeVeryVerboseFunctionName(&TEMP{0,1,2});

但偶然发现错误:

表达式必须是左值或函数指示符。

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

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

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

#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?)
}

发现如果某个结构T 有一个显式构造函数(就像上面代码中的A 一样),那么就可以获取@ 类型的大括号初始化临时地址987654329@ 并将其传递给接受指针 T* 的函数,但如果它没有指针(如 B),则出现上述错误,只能通过显式解决强制转换为 T 类型。

所以问题是:为什么B 需要如此奇怪的转换而A 不需要?

更新

现在很明显,将右值视为左值是 MSVS 中的扩展/功能/错误,是否有人愿意假装它实际上是一个功能(自 2010 年以来足以让 MS 维护它)并详细说明为什么 @987654335 的临时性@ 和B 需要以不同的方式传递以满足编译器吗?一定跟A的构造函数和B的缺失有关……

【问题讨论】:

  • 您没有收到“临时取地址”错误吗?尝试使用不同的编译器进行编译。
  • 注意 MSVC 有一个扩展允许 result of cast to be an lvalue 这似乎是同一个问题
  • fn(&A{ 1, 2 }); fn(&B {3, 4}); fn(&(B)B{ 3, 4 });都错了
  • @BЈовић 刚刚尝试使用 g++ (GCC) 4.9.3 并得到了 testbench.cpp:23:13: error: taking address of temporary [-fpermissive] fn(&amp;A {1, 2}); 和 testbench.cpp:25:17: error: taking address of temporary [-fpermissive] fn(&amp;(B) B {3, 4});
  • 这只是取一个临时地址,这是一个不同的 MSVC 扩展,allows binding temps to non-const references。

标签: c++ c++11 visual-c++ casting list-initialization


【解决方案1】:

你所做的在 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?)
   ^~~~~~~~~~~~~

All operands of &amp; must be lvalues,不是临时的。 MSVC 接受这些构造的事实是一个错误。根据上面Shafik指出的link,似乎MSVC错误地为这些创建了左值。

【讨论】:

【解决方案2】:
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;

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

SomeVeryVerboseFunctionName(&as_lvalue(TEMP{0,1,2}));

在某种意义上,as_lvalue 是一个相反的move。你可以称它为unmove,但这会让人感到困惑。

获取右值的地址在 C++ 中是非法的。上面将右值变成了左值,此时获取地址就变得合法了。

获取右值地址是非法的原因是这样的数据是要被丢弃的。指针只会在当前行结束之前保持有效(除非通过左值的强制转换创建右值)。这样的指针仅在极端情况下有用。但是,在 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;

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

MSVC 有一个旧的“错误”/“功能”,它在不应该的时候将许多东西视为左值,包括强制转换的结果。使用 /Za 禁用该扩展。这可能会导致其他工作代码无法编译。它甚至可能导致工作代码无法工作但仍然可以编译:我没有证明相反。

【讨论】:

  • @dyp 注意添加,请注意,Windows API 使用它们比添加的大多数情况更合理,mutable 添加变体。
  • 对于一个我绝望的初学者来说,知道如何合法地做到这一点绝对有用。我必须承认使用我原来的非法版本非常诱人,因为它非常简洁而且可以正常工作,但我什至还没有开始设想隐藏在 c++ 中的怪癖数量。谢谢!
  • @sunnymoon 缺少/Za 是危险的。找点乐子,看看这个:rextester.com/LDWW10898 -- 关闭/Za 和(double)d=0.0; 停止编译。
猜你喜欢
  • 2021-09-13
  • 2012-07-29
  • 1970-01-01
  • 2021-12-31
  • 1970-01-01
  • 2021-05-23
  • 1970-01-01
  • 2020-10-02
相关资源
最近更新 更多