为什么通过地址传递大括号初始化的临时变量需要显式转换为 MSVS 中的相同类型
Why does passing a brace-initialized temporary by address require explicit casting to the same type in MSVS
在处理 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});
但偶然发现错误:
expression must be an lvalue or function designator.
经过多次尝试,我终于想出了可以编译的代码(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
在上面的代码中具有),则可以采用T
类型的大括号初始化的临时地址,并将其传递给接受指针 T*
的函数,但如果它 没有指针 (如 B
),则出现上述错误,只能通过显式转换为类型 T
.
来克服
所以问题是:为什么 B
需要如此奇怪的转换而 A
不需要?
更新
现在很明显,将右值视为左值是 MSVS 中的一个 extension/feature/bug,有没有人愿意假装它实际上是一个功能(自 2010 年以来足以让 MS 维护它)并详细说明为什么临时A
和 B
需要以不同的方式传递才能满足编译器?肯定和A的构造函数有关,而B没有构造函数...
你所做的实际上在 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 &
must be lvalues, not temporaries. The fact that MSVC accepts these constructs is a bug. According to the link 上面的 Shafik 指出,似乎 MSVC 错误地为这些创建了左值。
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;
因为 returns 对数据的 const
引用更可能是正确的(你为什么要修改一个临时的?),而更长的一个(因此不太可能被使用)returns非const
版本。
MSVC 有一个旧的 "bug"/"feature",它将许多不应该的东西视为左值,包括强制转换的结果。使用 /Za
禁用该扩展。这可能会导致其他工作代码无法编译。甚至可能导致工作代码无法工作,仍然可以编译:我没有反证过。
在处理 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});
但偶然发现错误:
expression must be an lvalue or function designator.
经过多次尝试,我终于想出了可以编译的代码(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
在上面的代码中具有),则可以采用T
类型的大括号初始化的临时地址,并将其传递给接受指针 T*
的函数,但如果它 没有指针 (如 B
),则出现上述错误,只能通过显式转换为类型 T
.
所以问题是:为什么 B
需要如此奇怪的转换而 A
不需要?
更新
现在很明显,将右值视为左值是 MSVS 中的一个 extension/feature/bug,有没有人愿意假装它实际上是一个功能(自 2010 年以来足以让 MS 维护它)并详细说明为什么临时A
和 B
需要以不同的方式传递才能满足编译器?肯定和A的构造函数有关,而B没有构造函数...
你所做的实际上在 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 &
must be lvalues, not temporaries. The fact that MSVC accepts these constructs is a bug. According to the link 上面的 Shafik 指出,似乎 MSVC 错误地为这些创建了左值。
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;
因为 returns 对数据的 const
引用更可能是正确的(你为什么要修改一个临时的?),而更长的一个(因此不太可能被使用)returns非const
版本。
MSVC 有一个旧的 "bug"/"feature",它将许多不应该的东西视为左值,包括强制转换的结果。使用 /Za
禁用该扩展。这可能会导致其他工作代码无法编译。甚至可能导致工作代码无法工作,仍然可以编译:我没有反证过。