問題描述
是否可以在編譯時檢查模板類型是否已實例化,以便我可以在 enable_if 專業化中使用此信息?
假設我有
template 結構已知類型{};
如果 known_type 在編譯時實例化,我可以以某種方式定義一些值為 true 的 is_known_type 嗎?
如果您利用特定表達式可能會或可能不會在需要 constexpr
的地方使用這一事實,則可以這樣做,并且您可以查詢以查看您擁有的每個候選人的狀態.特別是在我們的例子中,沒有定義的 constexpr
不能作為常量表達式傳遞,而 noexcept
是常量表達式的保證.因此,noexcept(...)
返回 true
表示存在正確定義的 constexpr
.
本質上,這將 constexpr
s 視為 Yes/No 開關,并在編譯時引入狀態.
請注意,這幾乎是一個 hack,您將需要針對特定??編譯器的變通方法(請參閱前面的文章),并且此特定的基于 friend
的實現可能會被未來的修訂版視為格式錯誤標準.
除此之外...
用戶 Filip Roséen 在 他的文章專門針對它.
他的示例實現是,帶有引用的解釋:
constexpr int flag (int);
<塊引用>
constexpr 函數可以處于兩種狀態之一;要么是可用于常量表達式,或者不是 - 如果它缺少定義它自動屬于后一類 - 沒有其他狀態(除非我們考慮未定義的行為).
通常,constexpr 函數應該完全按照它們的方式來對待.是;函數,但我們也可以將它們視為單獨的句柄具有類似于 bool 類型的變量",其中每個變量"都可以具有兩個值之一;可用或不可用.
在我們的程序中,如果您認為 flag 就是這樣,它會有所幫助;一個手柄(不是函數).原因是我們永遠不會真正調用 flag在評估上下文中,我們只對其當前狀態感興趣.
template結構作者{朋友 constexpr int 標志(標簽){返回0;}};
<塊引用>
writer 是一個類模板,它在實例化時會創建一個函數在其周圍命名空間中的定義(具有簽名 int 標志(Tag),其中 Tag 是模板參數).
如果我們再次將 constexpr 函數視為某些變量,我們可以將 writer 的實例化視為無條件地將可用值寫入后面的變量朋友聲明中的函數.
templatestructdependent_writer : writer{ };
<塊引用>
如果你認為dependent_writer 看起來像一個相當無意義的間接;為什么不直接實例化writer我們想在哪里使用它,而不是通過dependent_writer?
- writer 的實例化必須依賴某些東西來防止立即實例化,并且;
- dependent_writer 用于可以將 bool 類型的值用作依賴項的上下文.
模板)>constexpr int f() {返回 B;}
<塊引用>
上面可能看起來有點奇怪,但其實很簡單;
- 如果 flag(0) 是一個常量表達式,將設置 B = true,否則 B = false,并且;
- 隱式實例化dependent_writer(sizeof 需要完全定義的類型).
行為可以用以下偽代碼表示:
IF [ `int flag (int)` 尚未定義 ]:SET `B` = `false`實例化`dependent_writer`返回`假`別的:SET `B` = `true`實例化`dependent_writer`返回`真`
最后是概念證明:
int main() {constexpr int a = f();constexpr int b = f();static_assert (a != b, "fail");}
<小時>
我將此應用于您的特定問題.這個想法是使用 constexpr
Yes/No 開關來指示一個類型是否已經被實例化.因此,您需要為您擁有的每種類型設置一個單獨的開關.
templatestruct inst_check_wrapper{朋友 constexpr int inst_flag(inst_check_wrapper<T>);};
inst_check_wrapper<T>
本質上為您提供的任何類型包裝了一個開關.這只是原始示例的通用版本.
template結構作者{朋友 constexpr int inst_flag(inst_check_wrapper<T>){返回0;}};
開關切換器與原始示例中的切換器相同.它提出了您使用的某種類型的開關的定義.為了便于檢查,添加一個輔助開關檢查器:
template ()))>constexpr bool is_instantiated(){返回 B;}
最后,類型注冊"為初始化.就我而言:
template 結構體{模板 >)>我的結構(){}};
只要請求特定的構造函數,開關就會打開.示例:
int main(){static_assert(!is_instantiated>(), "failure");MyStruct一個;static_assert(is_instantiated>(), "failure");}
在 Coliru 上直播.
Is it possible to check if a template type has been instantiated at compile time so that I can use this information in an enable_if specialization?
Let's say I have
template <typename T> struct known_type { };
Can I somehow define some is_known_type whose value is true if known_type is instantiated at compile time?
It's possible to do this if you leverage the fact that specific expressions may or may not be used in places where constexpr
s are expected, and that you can query to see what the state is for each candidate you have. Specifically in our case, the fact that constexpr
s with no definition cannot pass as constant expressions and noexcept
is a guarantee of constant expressions. Hence, noexcept(...)
returning true
signals the presence of a properly defined constexpr
.
Essentially, this treats constexpr
s as Yes/No switches, and introduces state at compile-time.
Note that this is pretty much a hack, you will need workarounds for specific compilers (see the articles ahead) and this specific friend
-based implementation might be considered ill-formed by future revisions of the standard.
With that out of the way...
User Filip Roséen presents this concept in his article dedicated specifically to it.
His example implementation is, with quoted explanations:
constexpr int flag (int);
A constexpr function can be in either one of two states; either it is usable in a constant-expression, or it isn't - if it lacks a definition it automatically falls in the latter category - there is no other state (unless we consider undefined behavior).
Normally, constexpr functions should be treated exactly as what they are; functions, but we can also think of them as individual handles to "variables" having a type similar to bool, where each "variable" can have one of two values; usable or not-usable.
In our program it helps if you consider flag to be just that; a handle (not a function). The reason is that we will never actually call flag in an evaluated context, we are only interested in its current state.
template<class Tag>
struct writer {
friend constexpr int flag (Tag) {
return 0;
}
};
writer is a class template which, upon instantiation, will create a definition for a function in its surrounding namespace (having the signature int flag (Tag), where Tag is a template-parameter).
If we, once again, think of constexpr functions as handles to some variable, we can treat an instantiation of writer as an unconditional write of the value usable to the variable behind the function in the friend-declaration.
template<bool B, class Tag = int>
struct dependent_writer : writer<Tag> { };
I would not be surprised if you think dependent_writer looks like a rather pointless indirection; why not directly instantiate writer where we want to use it, instead of going through dependent_writer?
- Instantiation of writer must depend on something to prevent immediate instantiation, and;
- dependent_writer is used in a context where a value of type bool can be used as dependency.
template<
bool B = noexcept (flag (0)),
int = sizeof (dependent_writer<B>)
>
constexpr int f () {
return B;
}
The above might look a little weird, but it's really quite simple;
- will set B = true if flag(0) is a constant-expression, otherwise B = false, and;
- implicitly instantiates dependent_writer (sizeof requires a completely-defined type).
The behavior can be expressed with the following pseudo-code:
IF [ `int flag (int)` has not yet been defined ]: SET `B` = `false` INSTANTIATE `dependent_writer<false>` RETURN `false` ELSE: SET `B` = `true` INSTANTIATE `dependent_writer<true>` RETURN `true`
Finally, the proof of concept:
int main () {
constexpr int a = f ();
constexpr int b = f ();
static_assert (a != b, "fail");
}
I applied this to your particular problem. The idea is to use the constexpr
Yes/No switches to indicate whether a type has been instantiated. So, you'll need a separate switch for every type you have.
template<typename T>
struct inst_check_wrapper
{
friend constexpr int inst_flag(inst_check_wrapper<T>);
};
inst_check_wrapper<T>
essentially wraps a switch for whatever type you may give it. It's just a generic version of the original example.
template<typename T>
struct writer
{
friend constexpr int inst_flag(inst_check_wrapper<T>)
{
return 0;
}
};
The switch toggler is identical to the one in the original example. It comes up with the definition for the switch of some type that you use. To allow for easy checking, add a helper switch inspector:
template <typename T, bool B = noexcept(inst_flag(inst_check_wrapper<T>()))>
constexpr bool is_instantiated()
{
return B;
}
Finally, the type "registers" itself as initialized. In my case:
template <typename T>
struct MyStruct
{
template <typename T1 = T, int = sizeof(writer<MyStruct<T1>>)>
MyStruct()
{}
};
The switch is turned on as soon as that particular constructor is asked for. Sample:
int main ()
{
static_assert(!is_instantiated<MyStruct<int>>(), "failure");
MyStruct<int> a;
static_assert(is_instantiated<MyStruct<int>>(), "failure");
}
Live on Coliru.
這篇關于編譯時模板實例化檢查的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!