std::make_shared 在使用位字段中的参数进行构造时无法编译
std::make_shared fails to compile when constructing with parameters from Bitfields
请考虑以下最小的可重新创建标准兼容代码
#include <vector>
#include <memory>
struct Foo
{
int m_field1;
Foo(int field1):m_field1(field1){};
};
typedef unsigned long DWORD;
typedef unsigned short WORD;
struct BitField {
struct {
DWORD Field1:31;
DWORD Field2:1;
} DUMMY;
};
int main()
{
std::vector<std::shared_ptr<Foo>> bar;
BitField *p = new BitField();
//This Line compiles
auto sp1 = std::shared_ptr<Foo>(new Foo((DWORD)p->DUMMY.Field1));
//But std::make_shared fails to compile
auto sp2 = std::make_shared<Foo>((DWORD)p->DUMMY.Field1);
return 0;
}
此代码无法在 VC11 Update 2 中编译,并显示以下错误消息
1>Source.cpp(23): error C2664: 'std::shared_ptr<_Ty> std::make_shared<Foo,DWORD&>(_V0_t)' : cannot convert parameter 1 from 'DWORD' to 'unsigned long &'
1> with
1> [
1> _Ty=Foo,
1> _V0_t=DWORD &
1> ]
我在 IDEONE 上交叉检查,它编译成功。我错过了一些明显的东西吗?
已打开连接错误 https://connect.microsoft.com/VisualStudio/feedback/details/804888/with-language-extension-enabled-vc11-an-explicit-cast-is-not-creating-an-rvalue-from-bit-fields
奇怪的问题。以下代码片段在 /Za
(禁用语言扩展(编译器标志下编译,但并非没有:
struct {
unsigned field:1;
} dummy = {0};
template<class T>
void foo(T&&){}
int main(){
foo((unsigned)dummy.field);
}
不带/Za
的错误:
错误 C2664: 'foo' : 无法将参数 1 从"无符号 int"转换为"无符号 int &">
这显然是一个错误,因为转换为unsigned
应该简单地创建一个右值,它不应该被推导为左值引用,也不应该被视为位字段。我有一种感觉,"右值绑定到左值引用"的扩展在这里发挥了作用。
请在Microsoft连接上提交错误报告。
这里更多的是评论而不是答案。它可能会对正在发生的事情有所了解。
Xeo的例子
struct {
unsigned field:1;
unsigned nonfield;
} dummy = {0};
template<class T>
void foo(T&&){}
第一步:类型推演。
[class.bit]/1 指定"位字段属性不是类成员类型的一部分"。因此,foo(dummy.field)
的类型推导会推导出要unsigned&
的模板参数。
第二步:过载解决。
虽然这里不是绝对必要的,但标准在 [over.ics.ref]/4 中有一个很好的例子
。[示例:具有"对
int
的左值引用"参数的函数可以是可行的候选函数,即使相应的参数是int
位字段。隐式转换序列的形成将int
位字段视为int
左值,并找到与参数的完全匹配。如果通过重载解析选择函数,则调用仍将格式不正确,因为禁止将非常量左值引用绑定到位字段 (8.5.3(。—结束示例 ]
因此,此函数的格式正确,将被选中,但调用的格式仍然不正确。
第三步:解决方法。
OP 的转换应该可以解决问题,foo( (unsigned)dummy.field )
,因为它产生一个右值,导致T
被推导为unsigned
,并且参数unsigned&&
是从临时初始化的。但是,如果源和目标具有相同的类型,MSVC 似乎会忽略左值到重值的转换。写作foo( (unsigned)dummy.nonfield )
也推断出T
T&
(即使有static_cast
(。
导T
到unsigned
而不是unsigned&
所需的左值到右值转换可以通过使用一元+
强制执行foo( +dummy.field )
:
的错误消息是正确的,因为它确实无法从您传入的值创建DWORD&
。位域的大小不适合作为DWORD
的真正引用。编译器拒绝你的程序是否正确,我不能说。
不过,这很容易解决。只需在调用 make_shared
时指定第二个模板参数:
auto sp2 = std::make_shared<Foo, int>(p->DUMMY.Field1);
我使用了int
,因为这是构造函数的参数类型。你可以说DWORD
;任何非引用数字类型可能就足够了。然后,您也可以放弃类型转换以DWORD
。它不再做任何事情。
位字段不能有引用,但有时被视为左值,因为它们可以分配给。位字段是混乱的IMO,您应该避免使用它们。
但是,如果您需要将位字段转换为与相同类型的右值完全相同的行为,则可以使用如下所示的函数。
template<class T>
T frombits(const T& x) {
return x;
}
//...
std::make_shared<Foo>(frombits(p->DUMMY.Field1));
我宁愿反对指定模板类型。如果可以,并且总是在预期的时候,让编译器推断类型。模板参数推导在 C++11 中可能会变得混乱,但它已被设计为运行良好,并且几乎在每种情况下都能正常工作。不要帮助编译器,不要认为你比它更了解;最终你会松懈。
- 非类型指针和引用模板参数,以及在编译时如何/为什么解析它们.c++
- C++模板-基于参数编译成员函数
- 无法使用模板参数编译提升元状态机
- lambda 和映射,引用参数 - 编译错误
- 为什么在函数参数编译中没有标识符的const关键字
- 无法使用模板参数编译类
- 数组作为函数参数 - 编译错误
- 使用 "using declaration" 扩展非类型模板参数包(模板可变参数编译时 SignalSlot 实现)
- 在 gcc 中使用数组参数编译外部"c"代码
- 为什么对齐的参数编译正确,但在运行时崩溃
- GCC上的亚型参考参数编译错误
- 是否可以将addr2line与使用发布优化参数编译的应用程序一起使用
- 尝试使用可变参数编译代码时出错
- C++ 线程获取引用参数编译失败
- 使用额外的模板参数编译函数
- 使用静态模板函数的默认参数编译错误
- 函数模板参数编译错误
- 谷歌模拟成员函数中的std::pair参数编译失败
- 模板模板参数-编译错误
- 防止基于模板参数编译函数(或部分函数)