當我第一次聽說 C 20 約束和概念時,我真的很興奮,到目前為止,我在測驗它們時一直很開心。最近想看看能不能用C 20的概念來測驗類或者函式的約束。例如:
template <int N>
requires (N > 0)
class MyArray { ... };
template <int N>
concept my_array_compiles = requires {
typename MyArray<N>;
};
my_array_compiles<1>; // true
my_array_compiles<0>; // false
起初我沒有任何問題,但我遇到了一個情況,即依賴函式中的 static_assert 阻止編譯,即使它出現在一個 requires 運算式中。下面是一個示例,說明了這一點:
template <bool b>
requires b
struct TestA {
void foo() {}
};
template <bool b>
struct TestB {
static_assert(b);
void foo() {}
};
template <template<bool> class T, bool b>
concept can_foo = requires (T<b> test) {
test.foo();
};
can_foo<TestA, true>; // true
can_foo<TestA, false>; // false
can_foo<TestB, true>; // true
// can_foo<TestB, false>; does not compile
對于大多數用例,TestA 和 TestB 的作業方式應該類似(盡管我發現 TestB<false> 可以用作型別,只要它沒有被實體化或取消參考)。但是,我的期望是,在 requires 運算式中失敗的 static_assert 會導致它的計算結果為 false。這對于使用仍然使用 static_assert 的庫代碼尤其重要。例如,std::tuple_element:
template <class T>
concept has_element_0 = requires {
typename tuple_element_t<0, T>;
};
has_element_0<tuple<int>>; // true
// has_element_0<tuple<>>; does not compile
當我將一個空元組傳遞給上述概念時,出現錯誤static_assert failed due to requirement '0UL < sizeof...(_Types)' "tuple_element index out of range"。我已經在 g 10.3.0 和 clang 12.0.5 上測驗過了。我能夠通過提供一個使用約束的包裝器來解決這個問題,但它有點違背了目的,因為我通過在更高級別強制執行相同的條件來阻止編譯器看到 static_assert。
template <size_t I, class T>
requires (I >= 0) && (I < tuple_size_v<T>)
using Type = tuple_element_t<I, T>;
template <class T>
concept has_element_0 = requires {
typename Type<0, T>;
};
has_element_0<tuple<int>>; // true
has_element_0<tuple<>>; // false
And it doesn't always work depending on how std::tuple_element is used:
template <size_t I, class T>
requires (I >= 0) && (I < tuple_size_v<T>)
tuple_element_t<I, T> myGet(const T& tup) {
return get<I>(tup);
}
template <class T>
concept has_element_0 = requires (T tup) {
myGet<0>(tup);
};
has_element_0<tuple<int>>; // true
// has_element_0<tuple<>>; does not compile
So ultimately my questions are: is this expected behavior that requires expressions don't take static_assert into account? If so, what was the reason for that design? And finally, is there a better way to accomplish my goal on classes with static_assert without using the above workaround?
Thanks for reading.
uj5u.com熱心網友回復:
是的,您與之互動的內容中沒有任何內容被檢查。只是宣告的直接背景關系。
在某些情況下,使用 decltype 會檢查某些構造的非直接背景關系,但任何錯誤仍然很難。
這樣做是為了減少對編譯器的要求。只有在所謂的“即時背景關系”中,編譯器才需要能夠在看到錯誤時干凈地退出并繼續編譯。
靜態斷言永遠不適合此目的。靜態斷言,如果命中,則結束編譯。
uj5u.com熱心網友回復:
如果您想避免靜態斷言(預計會結束編譯),那么您需要提供替代方案。設計概念后,為該概念的 not (!) 創建一個變體:
#include <tuple>
#include <variant>
template <std::size_t I, class T>
requires (I >= 0) && (I < std::tuple_size_v<T>)
using Type = std::tuple_element_t<I, T>;
template <class T>
concept has_element_0 = requires {
typename Type<0, T>;
};
bool test1()
{
return has_element_0<std::tuple<int>>; // true
}
bool test2()
{
return has_element_0<std::tuple<>>; // false
}
template <std::size_t I, class T>
requires (I >= 0) && (I < std::tuple_size_v<T>)
std::tuple_element_t<I, T> myGet_impl(const T& tup) {
return get<I>(tup);
}
template <class T>
concept alt_has_element_0 = requires (T tup) {
myGet_impl<0>(tup);
};
template <class T>
auto myGet0();
template <class T>
requires (alt_has_element_0<T>)
auto myGet0(const T& tup)
{
return myGet_impl<0, T>(tup);
}
auto test3()
{
std::tuple<int> X{7};
return myGet0(X); // true
}
template <class T>
requires (!alt_has_element_0<T>)
auto myGet0(const T& tup)
{
return std::monostate{};
}
auto test4()
{
std::tuple<> X;
return myGet0(X); // true
}
在這里看到它;
注意test4()編譯,上面的代碼玷污了如果我們不滿足概念的要求該怎么辦。我std::monostate為此從變體中偷走了。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/331238.html
標籤:c templates c 20 c -concepts type-constraints
上一篇:Go模板后處理:有可能嗎?
