6
\$\begingroup\$

I implemented concepts for container and allocator types. I referenced type requirements from here for container and here for allocator.

For the sake of simplicity, there are a lot of type aliases declared in the template because concept itself does not allow declaration.

I'm also concerned that many parts of the code are duplicate or irrelevant as of C++20.

For example, I excluded A::template rebind<U>::other from the requirements since it is removed in C++20. And, the allocator itself no longer requires methods other than allocate and deallocate.

Here is the entire code. Thanks in advance.

#include <utility>
#include <memory>
#include <iterator>
#include <type_traits>
#include <concepts>

namespace phd {

template<typename T>
concept copy_assignable = std::is_copy_assignable_v<T>;

template<typename T>
concept move_assignable = std::is_move_assignable_v<T>;

template<typename T>
concept nullable_pointer =
    std::equality_comparable<T> &&
    std::default_initializable<T> &&
    std::copy_constructible<T> &&
    copy_assignable<T> &&
    std::destructible<T> &&
    requires (T p, T q) {
        { T(nullptr) }   -> std::same_as<T>;
        { p = nullptr }  -> std::same_as<T&>;
        { p != q }       -> std::convertible_to<bool>;
        { p == nullptr } -> std::convertible_to<bool>;
        { nullptr == p } -> std::convertible_to<bool>;
        { p != nullptr } -> std::convertible_to<bool>;
        { nullptr != p } -> std::convertible_to<bool>;
    };

template<
    typename Alloc,
    typename T                  = Alloc::value_type,
    typename pointer            = std::allocator_traits<Alloc>::pointer,
    typename const_pointer      = std::allocator_traits<Alloc>::const_pointer,
    typename void_pointer       = std::allocator_traits<Alloc>::void_pointer,
    typename const_void_pointer =
        std::allocator_traits<Alloc>::const_void_pointer,
    typename size_type          = std::allocator_traits<Alloc>::size_type,
    typename difference_type    = std::allocator_traits<Alloc>::difference_type
>
concept allocator =
    nullable_pointer<pointer> &&
    std::random_access_iterator<pointer> &&
    std::contiguous_iterator<pointer> &&
    nullable_pointer<const_pointer> &&
    std::random_access_iterator<const_pointer> &&
    std::contiguous_iterator<const_pointer> &&
    std::convertible_to<pointer, const_pointer> &&
    nullable_pointer<void_pointer> &&
    std::convertible_to<pointer, void_pointer> &&
    nullable_pointer<const_void_pointer> &&
    std::convertible_to<pointer, const_void_pointer> &&
    std::convertible_to<const_pointer, const_void_pointer> &&
    std::convertible_to<void_pointer, const_void_pointer> &&
    std::unsigned_integral<size_type> &&
    std::signed_integral<difference_type> &&
    std::copy_constructible<Alloc> &&
    std::move_constructible<Alloc> &&
    copy_assignable<Alloc> &&
    move_assignable<Alloc> &&
    std::equality_comparable<Alloc> &&
    requires(Alloc a) {
        typename T;

        // *p
        {*std::declval<pointer>()} -> std::same_as<T&>;

        // *cp
        {*std::declval<const_pointer>()} -> std::same_as<const T&>;

        // static_cast<Alloc::pointer>(vp)
        requires
            std::same_as<
                decltype(static_cast<T*>(std::declval<void_pointer>())),
                pointer
            >;

        // static_cast<Alloc::const_pointer>(cvp)
        requires
            std::same_as<
                decltype(static_cast<const_pointer>(
                    std::declval<const_void_pointer>())),
                const_pointer
            >;

        // std::pointer_traits<Alloc::pointer>::pointer_to(r)
        {std::pointer_traits<pointer>::pointer_to(*std::declval<pointer>())}
            -> std::same_as<pointer>;

        {a.allocate(std::declval<size_type>())} -> std::same_as<pointer>;

        {a.deallocate(std::declval<pointer>(), std::declval<size_type>())}
            -> std::same_as<void>;
    };

template<typename T, typename Alloc>
concept erasable = requires(Alloc m, T* p) {
    {std::allocator_traits<Alloc>::destroy(m, p)} -> std::same_as<void>;
};

template<typename T, typename Alloc>
concept move_insertable = requires(Alloc m, T* p, T&& rv) {
    {std::allocator_traits<Alloc>::construct(m, p, rv)} -> std::same_as<void>;
};

template<typename T, typename Alloc>
concept copy_insertable =
    move_insertable<T, Alloc> &&
    requires(Alloc m, T* p, const T& v) {
        {std::allocator_traits<Alloc>::construct(m, p, v)}
            -> std::same_as<void>;
    };

template<typename Iter, typename Container>
concept container_iterator =
    std::same_as<Iter, typename Container::iterator> ||
    std::same_as<Iter, typename Container::const_iterator>;

template<
    typename Container,
    typename C               = Container,
    typename T               = C::value_type,
    typename Alloc           = C::allocator_type,
    typename value_type      = T,
    typename reference       = C::reference,
    typename const_reference = C::const_reference,
    typename iterator        = C::iterator,
    typename const_iterator  = C::const_iterator,
    typename difference_type = C::difference_type,
    typename size_type       = C::size_type
>
concept container =
    erasable<T, Alloc> &&
    requires() {
        typename reference;
        typename const_reference;
    } &&
    std::forward_iterator<iterator> &&
    std::convertible_to<iterator, const_iterator> &&
    std::forward_iterator<const_iterator> &&
    std::signed_integral<difference_type> &&
    std::same_as<difference_type,
        typename std::iterator_traits<iterator>::difference_type> &&
    std::same_as<difference_type,
        typename std::iterator_traits<const_iterator>::difference_type> &&
    std::unsigned_integral<size_type> &&
    std::default_initializable<C> &&
    std::copy_constructible<C> &&
    std::equality_comparable<C> &&
    std::swappable<C> &&
    copy_insertable<T, Alloc> &&
    std::equality_comparable<T> &&
    std::destructible<T> &&
    requires (C a) {
        { a.~C() }       -> std::same_as<void>;
        { a.begin() }    -> container_iterator<C>;
        { a.end() }      -> container_iterator<C>;
        { a.cbegin() }   -> std::same_as<const_iterator>;
        { a.cend() }     -> std::same_as<const_iterator>;
        { a.size() }     -> std::same_as<size_type>;
        { a.max_size() } -> std::same_as<size_type>;
        { a.empty() }    -> std::convertible_to<bool>;
    };

}
\$\endgroup\$
6
  • \$\begingroup\$ Add requires before requires (C a). Explanation: stackoverflow.com/questions/54200988/… \$\endgroup\$
    – isnullxbh
    Jan 26, 2021 at 7:52
  • \$\begingroup\$ @isnullxbh requires requires only applies to function and class. When declaring concept, a single requires clause serves as a boolean expression. \$\endgroup\$
    – PHD
    Jan 26, 2021 at 13:46
  • \$\begingroup\$ Check the following example. GCC doesn't compile it (without second requires), Clang generates a warning. \$\endgroup\$
    – isnullxbh
    Jan 26, 2021 at 14:05
  • \$\begingroup\$ @isnullxbh I think you misunderstood what I said. A single requires clause is used to apply constraints on subsequent stuff (function, class). The first requires from requires requires also does the same thing. But the latter one is a boolean constant expression. Unless you intentionally nest requires inside requires block just like the example you gave, a single requires is valid. \$\endgroup\$
    – PHD
    Jan 26, 2021 at 14:28
  • 1
    \$\begingroup\$ As stated here, requires does two things: specifying constraints and boolean expression. The one used in my code represents the latter. Therefore, there's nothing wrong with it. \$\endgroup\$
    – PHD
    Jan 26, 2021 at 14:31

1 Answer 1

2
\$\begingroup\$

I think you're perhaps overdoing the constraints (and perhaps trying to catch out Machiavelli, rather than to help Murphy). I'd write the nullable pointer type much more simply:

template<typename T>
concept nullable_pointer =
    std::regular<T> && std::assignable_from<T, nullptr_t>;

The allocator and container concepts have a lot of optional template parameters that aren't really useful to specify (and can be actively harmful). I recommend removing those and replacing their use with the current default values. This will give simpler error messages.

Quite a lot of container is included in std::ranges::common_range concept.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.