5

I would like an implementation of a C++ concept for the standard library containers.

Many thanks in advance!

user176168
  • 1,294
  • 1
  • 20
  • 30

4 Answers4

7

Fixed up Caleth's answer with the C++ concepts library:

template <class ContainerType> 
concept Container = requires(ContainerType a, const ContainerType b) 
{
    requires std::regular<ContainerType>;
    requires std::swappable<ContainerType>;
    requires std::destructible<typename ContainerType::value_type>;
    requires std::same_as<typename ContainerType::reference, typename ContainerType::value_type &>;
    requires std::same_as<typename ContainerType::const_reference, const typename ContainerType::value_type &>;
    requires std::forward_iterator<typename ContainerType::iterator>;
    requires std::forward_iterator<typename ContainerType::const_iterator>;
    requires std::signed_integral<typename ContainerType::difference_type>;
    requires std::same_as<typename ContainerType::difference_type, typename std::iterator_traits<typename
ContainerType::iterator>::difference_type>;
    requires std::same_as<typename ContainerType::difference_type, typename std::iterator_traits<typename
ContainerType::const_iterator>::difference_type>;
    { a.begin() } -> std::same_as<typename ContainerType::iterator>;
    { a.end() } -> std::same_as<typename ContainerType::iterator>;
    { b.begin() } -> std::same_as<typename ContainerType::const_iterator>;
    { b.end() } -> std::same_as<typename ContainerType::const_iterator>;
    { a.cbegin() } -> std::same_as<typename ContainerType::const_iterator>;
    { a.cend() } -> std::same_as<typename ContainerType::const_iterator>;
    { a.size() } -> std::same_as<typename ContainerType::size_type>;
    { a.max_size() } -> std::same_as<typename ContainerType::size_type>;
    { a.empty() } -> std::same_as<bool>;
};
user176168
  • 1,294
  • 1
  • 20
  • 30
  • 1
    I guess there should be no underscore aroud _ ContainerType _::iterator in the iterator_traits-related parts, or am I missing something? – alagner Jan 05 '21 at 00:07
  • 4
    The syntax `{ expression } -> typename ` [doesn't seem to compile](https://godbolt.org/z/5vhab4MG1). Is it maybe a feature of concepts that was removed before they were standardized? – BeeOnRope Aug 01 '22 at 21:43
6

There's the pre-concept Container requirements. As a concept that would look something like

template <class E>
concept default_erasable = requires(E * p) {
    std::destroy_at(p);
};

template <class E, class T, class A>
concept allocator_erasable = requires(A m, E * p) {
    requires std::same_as<typename T::allocator_type, typename std::allocator_traits<A>::rebind_alloc<E>>;
    std::allocator_traits<A>::destroy(m, p);
};

template <class T>
concept allocator_aware = requires (T a) {
    { a.get_allocator() } -> std::same_as<typename T::allocator_type>;
};

template <class T>
struct is_basic_string : std::false_type {};

template <class C, class T, class A>
struct is_basic_string<std::basic_string<C, T, A>> : std::true_type {};

template <class T>
constexpr bool is_basic_string_v = is_basic_string<T>::value;

template <class E, class T>
concept erasable = (is_basic_string_v<T> && default_erasable<E>)
                || (allocator_aware<T> && allocator_erasable<E, T, typename T::allocator_type>) 
                || (!allocator_aware<T> && default_erasable<E>);

template <class T>
concept container = requires(T a, const T b)
{
    requires std::regular<T>;
    requires std::swappable<T>;
    requires erasable<typename T::value_type, T>;
    requires std::same_as<typename T::reference, typename T::value_type &>;
    requires std::same_as<typename T::const_reference, const typename T::value_type &>;
    requires std::forward_iterator<typename T::iterator>;
    requires std::forward_iterator<typename T::const_iterator>;
    requires std::signed_integral<typename T::difference_type>;
    requires std::same_as<typename T::difference_type, typename std::iterator_traits<typename T::iterator>::difference_type>;
    requires std::same_as<typename T::difference_type, typename std::iterator_traits<typename T::const_iterator>::difference_type>;
    { a.begin() } -> std::same_as<typename T::iterator>;
    { a.end() } -> std::same_as<typename T::iterator>;
    { b.begin() } -> std::same_as<typename T::const_iterator>;
    { b.end() } -> std::same_as<typename T::const_iterator>;
    { a.cbegin() } -> std::same_as<typename T::const_iterator>;
    { a.cend() } -> std::same_as<typename T::const_iterator>;
    { a.size() } -> std::same_as<typename T::size_type>;
    { a.max_size() } -> std::same_as<typename T::size_type>;
    { a.empty() } -> std::convertible_to<bool>;
};

and you probably want to add requires std::ranges::range<T>; for concept partial ordering.

See it on coliru

Caleth
  • 52,200
  • 2
  • 44
  • 75
4

Fixed version that actually compiles in C++…:

template <class ContainerType>
concept Container = requires(ContainerType a, const ContainerType b)
{
    requires std::regular<ContainerType>;
    requires std::swappable<ContainerType>;
    requires std::destructible<typename ContainerType::value_type>;
    requires std::same_as<typename ContainerType::reference, typename ContainerType::value_type &>;
    requires std::same_as<typename ContainerType::const_reference, const typename ContainerType::value_type &>;
    requires std::forward_iterator<typename ContainerType::iterator>;
    requires std::forward_iterator<typename ContainerType::const_iterator>;
    requires std::signed_integral<typename ContainerType::difference_type>;
    requires std::same_as<typename ContainerType::difference_type, typename std::iterator_traits<typename ContainerType::iterator>::difference_type>;
    requires std::same_as<typename ContainerType::difference_type, typename std::iterator_traits<typename ContainerType::const_iterator>::difference_type>;
    { a.begin() } -> std::same_as<typename ContainerType::iterator>;
    { a.end() } -> std::same_as<typename ContainerType::iterator>;
    { b.begin() } -> std::same_as<typename ContainerType::const_iterator>;
    { b.end() } -> std::same_as<typename ContainerType::const_iterator>;
    { a.cbegin() } -> std::same_as<typename ContainerType::const_iterator>;
    { a.cend() } -> std::same_as<typename ContainerType::const_iterator>;
    { a.size() } -> std::same_as<typename ContainerType::size_type>;
    { a.max_size() } -> std::same_as<typename ContainerType::size_type>;
    { a.empty() } -> std::same_as<bool>;
};
static_assert(Container<std::vector<unsigned char>>);
static_assert(Container<std::string>);
donRumata
  • 59
  • 2
  • What's the difference between that and either of the other answers? And if there was an error in those other answers, why not just put a comment on them? – Etienne de Martel Jan 03 '22 at 05:37
  • @EtiennedeMartel The other answers don't compile. This one does. – Steve Ward May 17 '22 at 22:05
  • 1
    `std::destructible` isn't the same as [*Erasable*](https://en.cppreference.com/w/cpp/named_req/Erasable), `empty` is not required to be *exactly* `bool` – Caleth May 18 '22 at 09:10
1

If OP is interested, there are concepts for ranges (eg. container-like objects).

Found here: https://en.cppreference.com/w/cpp/ranges

If links are not acceptable, a quick Google search of "C++ ranges" will bring you to the page at cppreference.com.

You could do something like this (as I have done):

void a_function(std::ranges::forward_range auto container)
{
...
}

Or alternatively...

template<typename T>
concept YourContainer = requires(T container)
{
    requires std::ranges::range<T>;
    ...
};