【问题标题】:How to write a simple range concept?如何写一个简单的范围概念?
【发布时间】:2016-11-18 12:06:34
【问题描述】:

如何编写一个概念来描述启用基于范围的 for 循环的类型?

一种尝试是:

template < typename Range > concept bool RRange
    = requires(Range range) {{std::begin(range),std::end(range)};};

但我真正想要的是这样的:

template < typename Range > concept bool RRange
    = requires(Range range) {{for(auto&& item : range);};}; // compile error

也就是说,RRange 是表达式 for(auto&amp;&amp; item : range); 对其有效的所有类型的概念。实现这一目标的最佳方法是什么?

我正在使用带有g++ -std=c++1z -fconcepts 的 GCC7 快照。

【问题讨论】:

    标签: c++ range c++-concepts


    【解决方案1】:

    这是我在审查 [stmt.ranged] 时得出的结论。

    #include <utility>
    #include <experimental/type_traits>
    
    template <class T> using begin_non_mf_t = decltype(begin(std::declval<T>()));
    template <class T> using begin_mf_t     = decltype(std::declval<T>().begin());
    template <class T> using begin_t        = decltype(T::begin);
    template <class T> using end_non_mf_t   = decltype(end(std::declval<T>()));
    template <class T> using end_mf_t       = decltype(std::declval<T>().end());
    template <class T> using end_t          = decltype(T::end);
    
    template <class T>
    constexpr bool has_member_begin_or_end {
        std::experimental::is_detected_v<begin_mf_t,T> ||
        std::experimental::is_detected_v<begin_t,T> ||
        std::experimental::is_detected_v<end_mf_t,T> ||
        std::experimental::is_detected_v<end_t,T>};
    
    template <class T>
    std::add_lvalue_reference_t<T>  declref() noexcept;
    template <class T> using declref_t = decltype(declref<T>());
    
    template <class T>
    concept bool Range =
        requires /*Arrays*/ {
            requires std::is_array_v<T>;
            requires std::extent_v<T>!=0; // Extent is known.
        } ||
        /*Classes with member begin/end*/
        requires {
            requires std::is_class_v<T> && has_member_begin_or_end<T>;
        } &&
        requires (begin_mf_t<declref_t<T>> _begin,
                    end_mf_t<declref_t<T>> _end) {
            { _begin!=_end } -> bool;
            { *_begin } -> auto&&;
            { ++_begin };
        } ||
        /*Types with non-member begin/end*/
        requires {
            requires !std::is_class_v<T> || !has_member_begin_or_end<T>;
        } &&
        requires (begin_non_mf_t<declref_t<T>> _begin,
                    end_non_mf_t<declref_t<T>> _end) {
            { _begin!=_end } -> bool;
            { *_begin } -> auto&&;
            { ++_begin };
        };
    

    还有测试用例。

    #include <vector>
    
    // Evaluates to true or diagnoses which constraints failed.
    template <Range> constexpr bool is_range {true};
    
    static_assert(!Range<void>);
    static_assert(!Range<int>);
    static_assert(!Range<int*>);
    static_assert(!Range<int[]>);
    static_assert(is_range<int[1]>);
    static_assert(is_range<std::vector<int>>);
    
    struct A { };
    struct B {
        int begin;
    };
    struct C {
        int* begin();
        int* end();
    };
    struct D { };
    struct E {
        int end;
    };
    enum F { };
    struct G {
        int* begin() &&;
        int* end();
    };
    struct H {
        int* begin() &&;
        int* end() &&;
    };
    int* begin(D);
    int* end(D);
    int* begin(E);
    int* end(E);
    int* begin(F);
    int* end(F);
    int* begin(H);
    int* end(H);
    
    static_assert(!Range<A>);
    static_assert(!Range<B>);
    static_assert(is_range<C>);
    static_assert(is_range<D>);
    static_assert(!Range<E>);
    static_assert(is_range<F>);
    static_assert(!Range<G>);
    static_assert(!Range<H>);
    
    int main() { }
    

    【讨论】:

    • 添加一个警告是很好的,由于规范是如何编写的,用户代码只能尽最大努力将这个语言特性概念化(尽管是一个非常好的特性)。有(非常)病态的边缘情况无法被发现。
    • @LucDanton 根据 range-for 语义的要求,我重写了对命名变量进行操作的概念。但是,添加的测试用例失败了,我不知道为什么。会不会像您之前提到的那样是边缘案例?
    • 我不应该跳过使用以前的解决方案测试新的测试用例。我已经通过添加的declref 间接修复它。谢谢你所做的一切。
    • 是否有一个集中的地方(即一个具体的包含文件),这些类型的(标准?)概念定义是或将要位于的?
    【解决方案2】:

    根据P0587,这应该足够了:

    #include <vector>
    
    template<typename T>
    concept bool RangeForAble = requires (T t) {
       requires requires (decltype(begin(t)) b, decltype(end(t)) e) {
         b != e;
         ++b;
         *b;
       };
     };
    
    int main()
    {
    static_assert(RangeForAble<std::vector<int>>);
    static_assert(RangeForAble<double>);
    }
    

    【讨论】:

      【解决方案3】:

      在 C++20 中,它看起来像这样:

      template< class T >
      concept RealContainer = requires(T&& t) {
        std::begin(std::forward<T>(t));
        std::end  (std::forward<T>(t));
      };
      

      可能并不完美,但适用于 std::vector 和 C 数组,演示:https://gcc.godbolt.org/z/M4xhnqG46

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-04-14
        • 2021-01-22
        相关资源
        最近更新 更多