c++containersconceptc++23

Distinguish one and two-dimensional container using concepts


I want to distinguish one and two-dimensional containers using concepts. My first try was the following:

template<typename C>
concept Container1D = requires(C c) {
    std::begin(c);
    std::end(c);
    c[0];
};

template<typename C>
concept Container2D = requires(C c) {
    std::begin(c);
    std::end(c);
    c[0, 0]; // interpreted as comma-operator
};

But oviously this does not work, because the expression 0, 0 is interpreted as a comma-operator and so the second concept matches also one-dimensional container.

Is there a way to require a two-dimensional operator[a, b]?


Solution

  • Comma operator issues

    Not every compiler supports multi-dimensional subscript operators yet. At the time of writing, the compiler support is this:

    C++23 Feature GCC Clang MSVC
    Multi-dimensional subscript operator 12 15 (none)

    As a temporary workaround, you can use c.operator[](0, 0), which works as long as there is a multi-dimensional subscript overload in the standard library, even if the core language feature isn't supported yet.

    Issues with your concepts

    It's generally better to build concepts on top of other existing concepts. For instance, there is std::ranges::range:

    // in namespace std::ranges
    template<typename T>
    concept range = requires( T& t ) {
      ranges::begin(t); // equality-preserving for forward iterators
      ranges::end  (t);
    };
    
    // building on top of that...
    template<typename R>
    concept Range1D = std::ranges::range<R> && requires(R& r) {
        { r[0]; } -> std::ranges::range_value_t<R>;
    };
    

    However, this concept is really strange because r[0] implies that your container has random access, so using a stronger requirement like std::ranges::random_access_range would be better:

    template<typename R>
    concept Range1D = std::ranges::random_access_range<R> && requires(R& r) {
        { r[0]; } -> std::ranges::range_reference_t<R>;
    };
    
    template<typename R>
    concept Range2D = std::ranges::random_access_range<R> && requires(R& r) {
        { r.operator[](0, 0); } -> std::ranges::range_reference_t<R>;
    };