C++20 要求表达式不捕获 static_assert

C++20 requires expression does not catch static_assert

当我第一次听说 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 可以用作一种类型,只要它没有被实例化或取消引用)。但是,我的预期是 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

它并不总是有效,具体取决于 std::tuple_element 的使用方式:

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

所以最终我的问题是:这种需要表达式的预期行为是否没有考虑 static_assert?如果是这样,该设计的原因是什么?最后,有没有更好的方法可以在不使用上述解决方法的情况下使用 static_assert 在 类 上实现我的目标?

感谢阅读。

是的,您与之交互的内容中的任何内容都不会被检查。只是声明的直接上下文。

在某些情况下,使用 decltype 会检查某些构造的非直接上下文,但任何错误仍然很难。

这样做(回头)是为了减少对编译器的要求。只有在所谓的“即时上下文”中,编译器才需要能够在看到错误并继续编译时干净地退出。

静态断言永远不适合此目的。静态断言,如果命中,则结束编译。

如果你想避免静态断言(预计会结束编译)那么你需要提供一个替代方案。 设计概念后,为该概念的非(!)创建一个变体:

#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
}

看到了here;

编译test4()注意,上面的代码污损了如果我们不满足概念的要求怎么办。为此,我从变体中偷走了 std::monostate