Skip to content

Instantly share code, notes, and snippets.

@dexonsmith
Created July 8, 2017 02:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dexonsmith/509d31e0960d45958035b2bffd37bfc5 to your computer and use it in GitHub Desktop.
Save dexonsmith/509d31e0960d45958035b2bffd37bfc5 to your computer and use it in GitHub Desktop.
Demo of a 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
//
// Compile with ToT Clang using:
// clang++ -std=c++1z -fsyntax-only concepts-library-demo.cpp
//
//=============================================================================
#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
using namespace concepts;
//=============================================================================
// Try out some static asserts.
//=============================================================================
// Ensure that ill-formed void& is deftly avoided.
static_assert(Same<void, void>);
static_assert(ConvertibleTo<void, void>);
static_assert(!Boolean<void>);
static_assert(!CommonReference<void, void>);
static_assert(!WeaklyEqualityComparable<void, void>);
static_assert(!EqualityComparable<void>);
static_assert(!EqualityComparable<void, int>);
static_assert(!EqualityComparable<int, void>);
static_assert(!StrictTotallyOrdered<void>);
static_assert(!StrictTotallyOrdered<void, int>);
static_assert(!StrictTotallyOrdered<int, void>);
// Check a few other primitives.
static_assert(Same<bool, bool>);
static_assert(Same<bool&, bool&>);
static_assert(Same<const bool, const bool>);
static_assert(Same<const bool&, const bool&>);
static_assert(!Same<bool, bool&>);
static_assert(!Same<bool&, bool>);
static_assert(!Same<const bool, bool>);
static_assert(!Same<bool, const bool>);
static_assert(ConvertibleTo<bool, bool>);
static_assert(ConvertibleTo<const bool, bool>);
static_assert(ConvertibleTo<bool, const bool>);
static_assert(ConvertibleTo<bool&, const bool&>);
static_assert(!ConvertibleTo<const bool&, bool&>);
static_assert(Boolean<bool>);
static_assert(Boolean<int>);
static_assert(Boolean<float>);
static_assert(StrictTotallyOrdered<bool>);
static_assert(StrictTotallyOrdered<int>);
static_assert(StrictTotallyOrdered<float>);
// Check an empty class.
struct TestNone {};
static_assert(Same<TestNone, TestNone>);
static_assert(!Same<void, TestNone>);
static_assert(ConvertibleTo<void, void>);
static_assert(CommonReference<TestNone, TestNone>);
static_assert(!Boolean<TestNone>);
static_assert(!EqualityComparable<TestNone>);
static_assert(!StrictTotallyOrdered<TestNone>);
//=============================================================================
// Build some custom types.
//=============================================================================
// Build some types.
struct MyBool {
MyBool(bool);
operator bool() const;
friend MyBool operator==(MyBool, MyBool);
friend MyBool operator==(MyBool, bool);
friend MyBool operator==(bool, MyBool);
friend MyBool operator!=(MyBool, MyBool);
friend MyBool operator!=(MyBool, bool);
friend MyBool operator!=(bool, MyBool);
bool operator&&(MyBool) const;
bool operator||(MyBool) const;
bool operator&&(bool) const;
bool operator||(bool) const;
};
static_assert(Boolean<MyBool>);
static_assert(EqualityComparable<MyBool>);
static_assert(StrictTotallyOrdered<MyBool>); // implicit bool conversion.
struct MyEquality {
friend MyBool operator==(MyEquality, MyEquality);
friend MyBool operator!=(MyEquality, MyEquality);
};
static_assert(!Boolean<MyEquality>);
static_assert(EqualityComparable<MyEquality>);
static_assert(!StrictTotallyOrdered<MyEquality>);
struct MyOrdered {
friend MyBool operator==(MyOrdered, MyOrdered);
friend MyBool operator!=(MyOrdered, MyOrdered);
MyBool operator<(MyOrdered) const;
MyBool operator<=(MyOrdered) const;
MyBool operator>(MyOrdered) const;
MyBool operator>=(MyOrdered) const;
};
static_assert(!Boolean<MyOrdered>);
static_assert(EqualityComparable<MyOrdered>);
static_assert(StrictTotallyOrdered<MyOrdered>);
struct A {};
struct B : A {};
static_assert(CommonReference<const A&, const B&>);
static_assert(CommonReference<const A&, B&>);
static_assert(CommonReference<A&, B&>);
static_assert(CommonReference<const A, const B>);
static_assert(CommonReference<const A, B>);
static_assert(CommonReference<A, B>);
static_assert(!CommonReference<A&, const B&>);
static_assert(!CommonReference<A, const B>);
//=============================================================================
// Try out requires a bit.
//=============================================================================
#include <vector>
static_assert(requires<std::vector<int>>([](auto &&v) -> std::void_t<
decltype(v.begin() + 1),
decltype(*v.begin()),
decltype(v[8] = v[1])> {}));
static_assert(!requires<std::vector<int>>([](auto &&v) -> std::void_t<
decltype(*v)> {}));
static_assert(requires<std::vector<int>>([](auto &&v) -> std::enable_if_t<
Same<decltype(v[0]), int&> &&
Same<decltype(*v.begin()), int&> &&
!Same<decltype(*v.begin()), int> &&
ConvertibleTo<decltype(*v.begin()), const int&> &&
ConvertibleTo<decltype(*v.begin()), int> &&
ConvertibleTo<decltype(*v.begin()), int>> {}));
//=============================================================================
// Try to use an algorithms.
//=============================================================================
#include <algorithm>
// Check that all elements are true-ish.
template <class I, class = std::enable_if_t<
Boolean<typename std::iterator_traits<I>::value_type>>>
bool my_all_of(I first, I last) {
return std::all_of(first, last, [](auto &&x) { return x; });
}
void foo(std::vector<MyBool>& bools, std::vector<int>& ints,
std::vector<MyEquality>& equals, std::vector<MyOrdered>& ordered) {
// These work.
my_all_of(ints.begin(), ints.end());
my_all_of(bools.begin(), bools.end());
// These don't work, since they aren't convertible to bool.
//my_all_of(equals.begin(), equals.end());
//my_all_of(ordered.begin(), ordered.end());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment