Skip to content

Instantly share code, notes, and snippets.

@dexonsmith
Created July 7, 2017 07:06
Show Gist options
  • Save dexonsmith/6cf261056e670ca1a1df5828bfe3f7ae to your computer and use it in GitHub Desktop.
Save dexonsmith/6cf261056e670ca1a1df5828bfe3f7ae to your computer and use it in GitHub Desktop.
A pure library-based approach to describing concepts for C++
//=============================================================================
// A pure library-based approach to describing concepts for C++.
//
// Concepts based on a selection of those in the Ranges TS as described by:
// http://en.cppreference.com/w/cpp/experimental/ranges
//
// requires inspired by hana::is_valid:
// http://boostorg.github.io/hana/index.html#tutorial-introspection-is_valid
//=============================================================================
#pragma once
#include <type_traits>
namespace concepts {
//=============================================================================
// Preamble, not too interesting.
//=============================================================================
namespace detail {
/// Machinery to check if any type in the list is void.
template <class... Ts>
struct any_voids : std::false_type {};
template <class... Ts>
struct any_voids<void, Ts...> : std::true_type {};
template <class T, class... Ts>
struct any_voids<const T, Ts...> : any_voids<T, Ts...> {};
template <class T, class... Ts>
struct any_voids<volatile T, Ts...> : any_voids<T, Ts...> {};
template <class T, class... Ts>
struct any_voids<T, Ts...> : any_voids<Ts...> {};
template <class... Ts>
constexpr bool any_voids_v = any_voids<Ts...>::value;
/// Remove cv-qualifiers and any reference.
template <class T>
struct uncvref {
typedef std::remove_cv_t<std::remove_reference_t<T>> type;
};
template <class T> using uncvref_t = typename uncvref<T>::type;
} // namespace detail
//=============================================================================
// Define the requires function.
//=============================================================================
namespace detail {
/// Selected iff F(Args...) is valid; returns true.
///
/// Uses the same technique as \c hana::is_valid:
/// http://boostorg.github.io/hana/index.html#tutorial-introspection-is_valid
///
/// Which is similar to std::experimental::is_detected:
/// http://en.cppreference.com/w/cpp/experimental/is_detected
template <class F, class... Args,
class = decltype(std::declval<F&&>()(std::declval<Args&&>()...))>
constexpr bool requires_impl(int) {
return true;
}
/// Selected iff F(Args...) is invalid; returns false.
template <class F, class... Args>
constexpr bool requires_impl(...) {
return false;
}
} // namespace detail
/// Return true iff F(Args...) is valid.
template <class... Args, class F>
constexpr bool requires(F&&) {
// Only evaluate if there aren't any voids. void cannot be passed as an
// argument, so requires_impl would give a compile error instead of returning
// false.
if constexpr (!detail::any_voids_v<Args...>)
return detail::requires_impl<F&&, Args&&...>(int{});
return false;
}
//=============================================================================
// Define common_reference_t using requires.
//=============================================================================
namespace detail {
/// Check whether two types have a common reference.
template <class T, class U>
constexpr bool has_common_reference =
requires<T, U>([](auto&& x, auto&& y) -> decltype(true ? x : y) {});
/// Compute a common reference between two types, if it is valid.
template <class T, class U,
class = std::enable_if_t<has_common_reference<T, U>>>
struct common_reference {
typedef decltype(true ? std::declval<T>() : std::declval<U>()) type;
};
template <class T, class U>
using common_reference_t = typename common_reference<T, U>::type;
/// Compute a common reference between two types, or void if there is no such
/// reference.
///
/// Unlike common_reference, this does not cause a template-deduction failure
/// when it's used.
template <class T, class U, bool = has_common_reference<T, U>>
struct common_reference_or_void {
typedef common_reference_t<T, U> type;
};
template <class T, class U>
struct common_reference_or_void<T, U, false> {
typedef void type;
};
template <class T, class U>
using common_reference_or_void_t =
typename common_reference_or_void<T, U>::type;
} // namespace detail
//=============================================================================
// Rename a couple type traits to match concept names.
//=============================================================================
/// Concept for same types.
template <class T, class U>
constexpr bool Same = std::is_same_v<T, U>;
/// Concept for convertible types.
template <class T, class U>
constexpr bool ConvertibleTo = std::is_convertible_v<T, U>;
//=============================================================================
// Define a few higher-level concepts.
//=============================================================================
/// Concept for Booleans.
template <class T>
constexpr bool Boolean =
requires<const T>([](auto&& x) -> std::void_t<
decltype(bool(x)),
decltype(bool(!x))> {}) &&
requires<const T>([](auto&& x) -> std::enable_if_t<
ConvertibleTo<decltype(x), bool> &&
ConvertibleTo<decltype(!x), bool>> {}) &&
requires<const T, const T>([](auto&& t1, auto&& t2) -> std::enable_if_t<
Same<decltype(t1 && t2), bool> &&
Same<decltype(t1 || t2), bool> &&
ConvertibleTo<decltype(t1 == t2), bool> &&
ConvertibleTo<decltype(t1 != t2), bool>> {}) &&
requires<const bool, const T>([](auto&& b, auto&& x) -> std::enable_if_t<
Same<decltype(b && x), bool> &&
Same<decltype(b || x), bool> &&
Same<decltype(x && b), bool> &&
Same<decltype(x || b), bool> &&
ConvertibleTo<decltype(b == x), bool> &&
ConvertibleTo<decltype(b != x), bool> &&
ConvertibleTo<decltype(x == b), bool> &&
ConvertibleTo<decltype(x != b), bool>> {});
/// Concept for types whose equality comparisons return Boolean.
template <class T, class U>
constexpr bool WeaklyEqualityComparable =
requires<const T&, const U&>([](auto&& x, auto&& y) -> std::enable_if_t<
Boolean<decltype(x == y)> &&
Boolean<decltype(y == x)> &&
Boolean<decltype(x != y)> &&
Boolean<decltype(y != x)>> {});
// Avoid forming 'const void&'.
template <> constexpr bool WeaklyEqualityComparable<void, void> = false;
template <class T> constexpr bool WeaklyEqualityComparable<T, void> = false;
template <class T> constexpr bool WeaklyEqualityComparable<void, T> = false;
/// Concept for whether there's a valid common reference between two types.
template <class T, class U>
constexpr bool CommonReference =
requires<T, U>([](auto&& x, auto&& y) -> std::enable_if_t<
Same<detail::common_reference_t<decltype(x), decltype(y)>,
detail::common_reference_t<decltype(y), decltype(x)>>> {}) &&
// Use 'common_reference_or_void_t' to use T and U directly.
requires<T (&)()>([](auto&& f) ->
decltype(detail::common_reference_or_void_t<T, U>(f())) {}) &&
requires<U (&)()>([](auto&& f) ->
decltype(detail::common_reference_or_void_t<T, U>(f())) {});
/// Concept for having == and !=.
template <class T, class U = T>
constexpr bool EqualityComparable =
CommonReference<const T&, const U&> &&
EqualityComparable<T> &&
EqualityComparable<U> &&
EqualityComparable<detail::uncvref_t<
detail::common_reference_or_void_t<const T&, const U&>>> &&
WeaklyEqualityComparable<T, U>;
template <class T>
constexpr bool EqualityComparable<T> = WeaklyEqualityComparable<T, T>;
// Avoid forming 'const void&'.
template <> constexpr bool EqualityComparable<void, void> = false;
template <class T> constexpr bool EqualityComparable<T, void> = false;
template <class T> constexpr bool EqualityComparable<void, T> = false;
/// Concept for having ==, !=, <, >, <=, and >=.
template <class T, class U = T>
constexpr bool StrictTotallyOrdered =
CommonReference<const T&, const U&> &&
StrictTotallyOrdered<T> &&
StrictTotallyOrdered<U> &&
StrictTotallyOrdered<detail::uncvref_t<
detail::common_reference_or_void_t<const T&, const U&>>> &&
EqualityComparable<T, U> &&
requires<const T, const U>([](auto&& x, auto&& y) -> std::enable_if_t<
Boolean<decltype(x < y)> &&
Boolean<decltype(x > y)> &&
Boolean<decltype(x <= y)> &&
Boolean<decltype(x >= y)> &&
Boolean<decltype(y < x)> &&
Boolean<decltype(y > x)> &&
Boolean<decltype(y <= x)> &&
Boolean<decltype(y >= x)>> {});
template <class T>
constexpr bool StrictTotallyOrdered<T> =
EqualityComparable<T> &&
requires<const T, const T>([](auto&& t1, auto&& t2) -> std::enable_if_t<
Boolean<decltype(t1 < t2)> &&
Boolean<decltype(t1 > t2)> &&
Boolean<decltype(t1 <= t2)> &&
Boolean<decltype(t1 >= t2)>> {});
// Avoid forming 'const void&'.
template <> constexpr bool StrictTotallyOrdered<void, void> = false;
template <class T> constexpr bool StrictTotallyOrdered<T, void> = false;
template <class T> constexpr bool StrictTotallyOrdered<void, T> = false;
} // namespace concepts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment