為什么以下代碼的行為與注釋相同?
struct S
{
template<typename T, typename = std::enable_if_t<!std::is_constructible_v<S, T>>>
S(T&&){}
};
int main() {
S s1{1}; // OK
int i = 1;
S s2{i}; // OK
static_assert(std::is_constructible_v<S, int>); // ERROR (any compiler)
}
我知道要啟用建構式,斷言必須為假。但是在上面的例子中 S 仍然是從 int 構造的!標準說了什么,編譯器做了什么?
我假設在啟用模板建構式之前,S in notconstructible 所以std::is_constructible<S, int>實體化為假。這啟用了模板建構式,但也譴責 std::is_constructible<S, int> 始終測驗為假。
我還用我自己的(偽?)版本的 std::is_constructible 進行了測驗:
#include <type_traits>
template<typename, typename T, typename... ARGS>
constexpr bool isConstructibleImpl = false;
template<typename T, typename... ARGS>
constexpr bool isConstructibleImpl<
std::void_t<decltype(T(std::declval<ARGS>()...))>,
T, ARGS...> =
true;
template<typename T, typename... ARGS>
constexpr bool isConstructible_v = isConstructibleImpl<void, T, ARGS...>;
struct S
{
template<typename T, typename = std::enable_if_t<!isConstructible_v<S, T>>>
S(T&&){}
};
int main() {
S s1{1}; // OK
int i = 1;
S s2{i}; // OK
static_assert(std::is_constructible_v<S, int>); // OK
}
我想這是因為現在 std::is_constructible 沒有在建構式中為 SFINAE 犧牲。isConstructible 被犧牲了。
這讓我想到了第二個問題:最后一個例子是在建構式上執行 SFINAE 而不破壞 std::is_constructible 的好方法嗎?
理性:我最終嘗試使用 SFINAE 模式告訴編譯器如果任何其他可用建構式匹配(尤其是默認建構式),即使不完美(例如,const &引數應該匹配&引數并且模板建構式應該不被認為是更好的匹配)。
uj5u.com熱心網友回復:
您的第一個代碼示例是未定義的行為,因為S它本身的宣告中不是一個完整的型別。std::is_constructible_v但是要求所有涉及的型別都是完整的:
請參閱cppreference.com中的這些段落:
T 和引數包 Args 中的所有型別均應為完整型別、(可能是 cv 限定的)void 或未知邊界陣列。否則,行為未定義。
如果上述模板的實體化直接或間接依賴于不完整的型別,并且如果假設該型別已完成,則該實體化可能會產生不同的結果,則行為未定義。
這是有道理的,因為為了讓編譯器知道是否可以構造某種型別,它需要知道完整的定義。在您的第一個示例中,代碼是一種遞回:編譯器需要通過檢查其自身依賴的建構式等來確定是否S可以從中構造。TSis_constructible
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/515138.html
標籤:C 模板类型特征
下一篇:如何定義依賴于模板引數的型別
