【问题标题】:C++20 requires expression does not catch static_assertC++20 需要表达式不捕获 static_assert
【发布时间】:2021-10-15 19:11:52
【问题描述】:

当我第一次听说 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 &lt; 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 实现我的类目标?

感谢阅读。

【问题讨论】:

  • sfinae 一样,仅捕获“即时上下文”中的错误。大概是为了避免在大型模板上进行检查过于昂贵。
  • @HolyBlackCat 我相信您的回答是正确的,但标准中的哪个地方这么说?
  • @Barry 我没有看到任何暗示它在约束检查期间适用的内容。 “替换发生在函数类型和模板参数声明中使用的所有类型和表达式中。”要求表达式通常不会出现在这些地方。

标签: c++ templates c++20 c++-concepts type-constraints


【解决方案1】:

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

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

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

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

【讨论】:

    【解决方案2】:

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

    #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

    【讨论】:

      猜你喜欢
      • 2020-11-29
      • 2021-06-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-19
      相关资源
      最近更新 更多