簡體   English   中英

使用Bitfields中的參數構造時,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上交叉檢查,並且編譯成功。 我錯過了一些明顯的東西嗎

打開了連接Bug https://connect.microsoft.com/VisualStudio/feedback/details/804888/with-language-extension-enabled-vc11-an-explicit-cast-is-not-creating-an-rvalue-from位字段

這是一個奇怪的。 下面的代碼片段在/Za (禁用語言擴展)編譯器標志下編譯,但不是沒有:

struct {
  unsigned field:1;
} dummy = {0};

template<class T>
void foo(T&&){}

int main(){
  foo((unsigned)dummy.field);
}

沒有/Za出錯:

錯誤C2664:'foo':無法將參數1從'unsigned int'轉換為'unsigned int&'

這顯然是一個錯誤,因為轉換為unsigned應該只創建一個rvalue,它不應該推導為左值引用,不應該被視為位字段。 我有一種感覺“rvalues綁定到左值參考”的擴展在這里起作用。

請提交有關Microsoft Connect的錯誤報告。

編譯器的錯誤消息是正確的,因為它實際上無法根據您傳入的值創建DWORD& 。位域不是正確的大小,是對DWORD真正引用。 編譯器是否正確拒絕您的程序,我不能說。

但是,這很容易解決。 調用make_shared時,只需指定第二個模板參數:

auto sp2 = std::make_shared<Foo, int>(p->DUMMY.Field1);

我使用int因為這是構造函數的參數類型。 你可以說DWORD ; 任何非引用數字類型都可能就足夠了。 然后,您也可以放棄將類型轉換為DWORD 它沒有做更多的事情。

這里有更多的評論而不是答案。 它可能會對正在發生的事情有所了解。

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 ) ,因為它產生一個rvalue,導致T被推導為unsigned ,並且參數unsigned&&從臨時初始化。 但是,如果源和目標具有相同的類型,MSVC似乎忽略了左值到右值的轉換。 foo( (unsigned)dummy.nonfield )推導T作為T&以及(即使有static_cast )。

T推導為unsigned而非unsigned&所需的左值到右值轉換可以通過使用一元+foo( +dummy.field )來強制執行

位字段不能有引用,但有時會像左值一樣處理,因為它們可以分配給它們。 比特字段是混亂的IMO,你應該避免它們。

但如果您需要將位域轉換為與同一類型的右值完全相同的行為,則可以使用如下所示的函數。

template<class T>
T frombits(const T& x)  {
    return x;
}
//...
std::make_shared<Foo>(frombits(p->DUMMY.Field1));

我寧願反對指定模板類型。 如果可以,並且總是在預期的時候,讓編譯器推斷出類型。 模板參數推導在C ++ 11中可能會變得混亂,但它已被設計為非常好用,幾乎在所有情況下都能正常工作。 不要幫助編譯器,不要認為你比它更清楚; 最終你會松動。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM