Skip to content

Instantly share code, notes, and snippets.

@airglow923
Last active May 17, 2023 04:36
Show Gist options
  • Save airglow923/424c112543f984a7657e85af4c93aa46 to your computer and use it in GitHub Desktop.
Save airglow923/424c112543f984a7657e85af4c93aa46 to your computer and use it in GitHub Desktop.
User-defined concepts for container and allocator types using C++20 concepts
#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 boolean_testable_impl = std::convertible_to<T, bool>;
template <typename T>
concept boolean_testable =
boolean_testable_impl<T> &&
requires (T&& t) {
{ !std::forward<T>(t) } -> boolean_testable_impl;
};
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 } -> boolean_testable;
{ p == nullptr } -> boolean_testable;
{ nullptr == p } -> boolean_testable;
{ p != nullptr } -> boolean_testable;
{ nullptr != p } -> boolean_testable;
};
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>;
};
}
@airglow923
Copy link
Author

airglow923 commented Aug 24, 2020

Usage:

template<
    typename T,
    phd::allocator Alloc,
    template<typename, typename> typename Container
>
    requires phd::container<Container<T, Alloc>>
void do_with_container(const Container<T, Alloc>& container)
{
    // operations
}

Example:

#include <iostream>
#include <vector>

#include "phd_concepts.hpp"

template <typename T, phd::allocator Alloc,
          template <typename, typename> typename Container>
  requires phd::container<Container<T, Alloc>>
auto
do_with_container(const Container<T, Alloc> &container) -> void {
  for (auto &&e : container) {
    std::cout << e;
  }
}

auto
main() -> int {
  std::vector v{1, 2, 3};

  do_with_container(v);
}

@jhunterkohler
Copy link

Two things I notice off the bat:

  1. Operations with std::nullptr_t on nullable_pointer also need to check the const qualified const std::nullptr_t.
  2. std::convertible_to<bool> is not the same as boolean-testable. You would define boolean-testable as,
template <class T>
concept boolean_testable =
    std::convertible_to<T, bool> &&
    requires (T &&t) { { !std::forward<T>(t) } -> std::convertible_to<bool>; };

@airglow923
Copy link
Author

Hi @HunterKohler, thanks for telling me what I missed. I will fix the errors

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment