簡體   English   中英

通過typedef強制模板實例化:在g ++上成功,在Visual C ++上失敗

[英]Force template instantiation via typedef : success at g++ , fail at Visual C++

我想強制模板實例化。
以下代碼在g ++( http://coliru.stacked-crooked.com/a/33986d0e0d320ad4 )上有效(打印1 )。
但是,它在Visual C ++( https://rextester.com/WGQG68063 )上打印錯誤的結果( 0 )。

#include <iostream>
#include <string>
template <int& T>struct NonTypeParameter { };

//internal implementation
int lala=0;
template <typename T> struct Holder{
    static int init;
};
template <typename T> int Holder<T>::init = lala++;

//tool for user 
template <typename T> struct InitCRTP{ 
    using dummy=NonTypeParameter<Holder<T>::init>;
};

class WantInit : public InitCRTP<WantInit>{};//user register easily
int main(){
    std::cout << lala << std::endl;
}

是Visual C ++編譯器錯誤還是一種未定義的行為?
如果它是Visual C ++錯誤,如何解決(同時仍保持美觀)?

編輯:更改類->結構,建議使用Max Langhof(和很多人)。 謝謝。

賞金原因

通過StoryTellerMaxim Egorushkin的相反解決方案以及他們的深入討論(謝謝!),這聽起來像是C ++規則的模糊領域。

如果是Visual C ++錯誤,我希望這個問題一定可以報告。

而且,我仍然希望有一個不錯的解決方法,因為此技術對於自定義type-id生成非常有用。 顯式實例化不是那么方便。

注意:我授予Kaenbyou Rin賞金,因為對我來說,這很容易理解。
這並不意味着其余答案不太正確或用處不大。
我仍然不確定哪個是正確的。 讀者應謹慎行事。
為了安全起見,我假設我暫時無法使用該功能。 感謝大家。

肯定有一個編譯器錯誤。 我們可以通過InitCRTP更改InitCRTP來驗證它:

template <typename T, typename = NonTypeParameter<Holder<T>::init>>
struct InitCRTP {
};

現在引用任何InitCRTP<T>專長必須使用Holder<T>::init來確定第二個模板參數。 反過來,這應強制實例化Holder<T>::init ,但是VS不會實例化該實例

通常,使用CRTP類作為基礎應該實例化該類內部的所有聲明,包括dummy的聲明。 因此,這也應該起作用。

我們可以進一步驗證。 當用作基礎時,成員函數的聲明與類一起實例化:

template <typename T> struct InitCRTP{
    using dummy=NonTypeParameter<Holder<T>::init>;
    void dummy2(dummy);
};

盡管如此, VC ++還是很固執 考慮到所有這些,以及Clang和GCC都表現出的行為,這是一個VC ++錯誤。

class WantInit : public InitCRTP<WantInit>既不實例化InitCRTP<WantInit>::dummy也不對Holder<WantInit>::init實例化,因為程序中實際未使用它們。 代碼中的隱式實例化鏈不需要實例化Holder<T>::init ,請參見隱式實例化

這適用於類模板的成員除非在程序中使用了該成員,否則不會實例化該成員,並且不需要定義。

解決方法是使用顯式模板實例化

template struct Holder<void>;

這導致Holder<void>及其所有非模板成員都被實例化。

或者,您可以僅實例化Holder<T>::init成員,例如:

static_cast<void>(Holder<void>::init);

IMO,gcc和clang過於渴望實例化未引用的內容。 這樣的行為不會破壞或拒絕有效的代碼,因此這幾乎不是錯誤,但是取決於這種特定行為的副作用是脆弱且不可移植的。

讓我們嘗試肯定使用ODR的init成員。

#include <iostream>
#include <string>

int lala=0;
template <typename T> struct Holder{
    static int init;
};
template <typename T> int Holder<T>::init = lala++;

template <typename T> struct InitCRTP{
    InitCRTP() { (void)Holder<T>::init; }
};

class WantInit : public InitCRTP<WantInit>{};
int main(){
    std::cout << lala << std::endl;
    // WantInit w;  <---------------------------- look here
}

現在,如果注釋掉的行未注釋,則程序的結果將更改。 任何對象的IMHO模板實例化狀態或ODR使用狀態都不能取決於是否調用了某些非模板函數(在這種情況下為WantInit構造函數)。 我會說有一種強烈的臭蟲味。

我相信@MaximEgorushkin對dummy對象沒有真正實例化的事實是正確的。

聲明了dummy (因為它是類型別名聲明 ),並且為了聲明該別名,聲明了NonTypeParameter<Holder<T>::init> 為了聲明NonTypeParameter<Holder<T>::init> ,必須聲明其模板參數Holder<T>::init ,因此也要聲明Holder<T>

該標准要求在實例化模板類時,必須定義其刪除的成員函數。 [溫度規格]

類模板專業化的隱式實例化導致:[...]

-刪除成員函數,無作用域成員枚舉和成員匿名聯合的定義的隱式實例化。

並且對void的引用應導致編譯錯誤。

我們可以使用它來測試特定模板是否專用。

#include <iostream>
#include <string>
template <int& T, typename U> struct NonTypeParameter { 
    U& f() = delete;
};

//internal implementation
int lala = 0;
template <typename T> struct Holder {
    T& f() = delete;
    static int init;
};
template <typename T> int Holder<T>::init = lala++;

//tool for user 
template <typename T> struct InitCRTP {
    using dummy = NonTypeParameter<Holder<T>::init, void>;
};

class WantInit : public InitCRTP<WantInit> {};//user register easily
int main() {
    std::cout << lala << std::endl;
}

由於僅聲明而非實例化NonTypeParameter<Holder<T>::init, void>因此將編譯此代碼。

但是如果我們將class WantInit : public InitCRTP<WantInit>更改class WantInit : public InitCRTP<WantInit>

class WantInit : public InitCRTP<void>

無法在MSVC,g ++和clang中進行編譯。

這是因為NonTypeParameter<Holder<void>::init, void>需要Holder<void>的隱式實例化。

OP遇到的問題完全是由於MSVC對Holder<T>::init被ODR使用不了解:

#include <iostream>

template <int& T> struct NonTypeParameter { };

int lala = 0;

template <typename T> struct Holder {
    static int init;
};

template <typename T> int Holder<T>::init = lala++;

int main() {
    NonTypeParameter<Holder<int>::init> odr;
    std::cout << lala << std::endl;
}

MSVC將輸出0 這意味着它沒有意識到Holder<int>::init已被ODR使用。

編譯器資源管理器鏈接

暫無
暫無

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

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