[英]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(和很多人)。 謝謝。
通過StoryTeller和Maxim 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.