Yes, lite concepts mostly dress SFINAE. Plus it allows deeper introspection to provide better overload. However, this only works if concept predicates are defined as concept bool . Improved overloading does not work with current concept predicates, but conditional overloading can be used. Let's see how we can define predicates, restrict patterns and overload functions in C ++ 14. It's quite a long time, but it tells how to create all the tools necessary to accomplish this in C ++ 14.
Predicate Definition
Firstly, it is pretty ugly to read a predicate with all std::declval and decltype everywhere. Instead, we can take advantage of the fact that we can restrict the function using the trailing type (from Eric Nieblers blog post here ), like this:
struct Incrementable { template<class T> auto requires_(T&& x) -> decltype(++x); };
So, if ++x not valid, then the requires_ member function cannot be called. Thus, we can create a models attribute that simply checks if requires_ be called using void_t :
template<class Concept, class Enable=void> struct models : std::false_type {}; template<class Concept, class... Ts> struct models<Concept(Ts...), void_t< decltype(std::declval<Concept>().requires_(std::declval<Ts>()...)) >> : std::true_type {};
Bounding Patterns
Therefore, when we want to restrict the template based on the concept, we still have to use enable_if , but we can use this macro to make it cleaner:
#define REQUIRES(...) typename std::enable_if<(__VA_ARGS__), int>::type = 0
Thus, we can define an increment function that is limited based on the concept of Incrementable :
template<class T, REQUIRES(models<Incrementable(T)>())> void increment(T& x) { ++x; }
So, if we call increment what is not Incrementable , we get this error:
test.cpp:23:5: error: no matching function for call to 'incrementable' incrementable(f); ^~~~~~~~~~~~~ test.cpp:11:19: note: candidate template ignored: disabled by 'enable_if' [with T = foo] template<class T, REQUIRES(models<Incrementable(T)>())> ^
Overload Functions
Now, if we want to overload, we want to use conditional overload. Suppose we want to create std::advance using continuity predicates, we could define it like this (for now, we will ignore the decreasing case):
struct Incrementable { template<class T> auto requires_(T&& x) -> decltype(++x); }; struct Advanceable { template<class T, class I> auto requires_(T&& x, I&& i) -> decltype(x += i); }; template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())> void advance(Iterator& it, int n) { it += n; } template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())> void advance(Iterator& it, int n) { while (n--) ++it; }
However, this causes an ambiguous overload (in literal terms, it will still be an ambiguous overload if we do not change our predicates to refer to other predicates in the concept bool ) when it is used with the std::vector iterator. We want to place an order that we can do using conditional overload. You might consider writing something like this (which is unacceptable with C ++):
template<class Iterator> void advance(Iterator& it, int n) if (models<Advanceable(Iterator, int)>()) { it += n; } else if (models<Incrementable(Iterator)>()) { while (n--) ++it; }
So, if the first function is not called, it will call the next function. So let's start by implementing it for two functions. We will create a class called basic_conditional that takes two function objects as template parameters:
struct Callable { template<class F, class... Ts> auto requires_(F&& f, Ts&&... xs) -> decltype( f(std::forward<Ts>(xs)...) ); }; template<class F1, class F2> struct basic_conditional {
So now this means that we need to define our functions as objects of objects:
struct advance_advanceable { template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())> void operator()(Iterator& it, int n) const { it += n; } }; struct advance_incrementable { template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())> void operator()(Iterator& it, int n) const { while (n--) ++it; } }; static conditional<advance_advanceable, advance_incrementable> advance = {};
So, if we try to use it with std::vector :
std::vector<int> v = { 1, 2, 3, 4, 5, 6 }; auto iterator = v.begin(); advance(iterator, 4); std::cout << *iterator << std::endl;
It will compile and print 5 .
However, std::advance actually has three overloads, so we can use basic_conditional to implement a conditional that works for any number of functions using recursion:
template<class F, class... Fs> struct conditional : basic_conditional<F, conditional<Fs...>> {}; template<class F> struct conditional<F> : F {};
So now we can write the full std::advance as follows:
struct Incrementable { template<class T> auto requires_(T&& x) -> decltype(++x); }; struct Decrementable { template<class T> auto requires_(T&& x) -> decltype(--x); }; struct Advanceable { template<class T, class I> auto requires_(T&& x, I&& i) -> decltype(x += i); }; struct advance_advanceable { template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())> void operator()(Iterator& it, int n) const { it += n; } }; struct advance_decrementable { template<class Iterator, REQUIRES(models<Decrementable(Iterator)>())> void operator()(Iterator& it, int n) const { if (n > 0) while (n--) ++it; else { n *= -1; while (n--) --it; } } }; struct advance_incrementable { template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())> void operator()(Iterator& it, int n) const { while (n--) ++it; } }; static conditional<advance_advanceable, advance_decrementable, advance_incrementable> advance = {};
Overload with Lambdas
However, in addition, we could use lambdas for writing instead of function objects, which can help make it cleaner for writing. Therefore, we use the STATIC_LAMBDA macro to create lambdas at compile time:
struct wrapper_factor { template<class F> constexpr wrapper<F> operator += (F*) { return {}; } }; struct addr_add { template<class T> friend typename std::remove_reference<T>::type *operator+(addr_add, T &&t) { return &t; } }; #define STATIC_LAMBDA wrapper_factor() += true ? nullptr : addr_add() + []
And add the make_conditional function, which is constexpr :
template<class... Fs> constexpr conditional<Fs...> make_conditional(Fs...) { return {}; }
Then we can now write the advance function as follows:
constexpr const advance = make_conditional( STATIC_LAMBDA(auto& it, int n, REQUIRES(models<Advanceable(decltype(it), int)>())) { it += n; }, STATIC_LAMBDA(auto& it, int n, REQUIRES(models<Decrementable(decltype(it))>())) { if (n > 0) while (n--) ++it; else { n *= -1; while (n--) --it; } }, STATIC_LAMBDA(auto& it, int n, REQUIRES(models<Incrementable(decltype(it))>())) { while (n--) ++it; } );
This is a bit more compact and readable than using functional object versions.
In addition, we could define a modeled function to reduce decltype ugliness:
template<class Concept, class... Ts> constexpr auto modeled(Ts&&...) { return models<Concept(Ts...)>(); } constexpr const advance = make_conditional( STATIC_LAMBDA(auto& it, int n, REQUIRES(modeled<Advanceable>(it, n))) { it += n; }, STATIC_LAMBDA(auto& it, int n, REQUIRES(modeled<Decrementable>(it))) { if (n > 0) while (n--) ++it; else { n *= -1; while (n--) --it; } }, STATIC_LAMBDA(auto& it, int n, REQUIRES(modeled<Incrementable>(it))) { while (n--) ++it; } );
Finally, if you are interested in using existing library solutions (instead of riding on your own, as I showed). There is a Tick library that provides a framework for defining concepts and restricting patterns. And the Fit library can handle functions and overload.