Created
July 8, 2017 02:04
-
-
Save dexonsmith/509d31e0960d45958035b2bffd37bfc5 to your computer and use it in GitHub Desktop.
Demo of a library-based approach to describing concepts for C++
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//============================================================================= | |
// 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