std::make_shared 在使用位字段中的参数进行构造时无法编译

std::make_shared fails to compile when constructing with parameters from Bitfields

本文关键字:参数 编译 shared make 字段 std      更新时间:2023-10-16

请考虑以下最小的可重新创建标准兼容代码

#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(。

Tunsigned而不是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 中可能会变得混乱,但它已被设计为运行良好,并且几乎在每种情况下都能正常工作。不要帮助编译器,不要认为你比它更了解;最终你会松懈。