This post contains quite advanced material. I assume you are already familiar with Concepts Lite. For an overview of what Concepts Lite is, I recommend this proposal. Also, I have found this blog very useful regarding the details of and issues with concepts’ mechanics. One could look at Concepts Lite as three features:

A superior alternative to enable_if (or overload hiding). The subsumption relation that enables the additional control of partial ordering in the overload resolution process. A convenient tool for building compile-time predicates that check for valid types and expressions.

In this post I will only focus on the first feature, and try to answer the question, “what do we need Concepts Lite for, given that we already have std::enable_if (and SFINAE)?”

The power of enable_if

Indeed, enable_if is quite powerful, and gives us the ability to define things like ‘conditionally explicit constructor’:

template <typename T> class Wrapper { public: template <typename U, ENABLE_IF(std::is_convertible<U, T>::value)> Wrapper (U && v); template <typename U, ENABLE_IF(!std::is_convertible<U, T>::value)> explicit Wrapper (U && v); };

I use a hand-crafted macro ENABLE_IF to make the examples focused, it is defined as:

# define ENABLE_IF(...) \ typename std::enable_if<(__VA_ARGS__), bool>::type = true

Now, at this point, depending on how much you have been exposed to SFINAE-based tricks like this you may either respond “yes, this is how you declare such overloads,” or say “no, do you really expect me to read or write code like this?” Personally, I am so used to these tricks, that I find nothing difficult or unusual about them, but that may only prove how corrupt I have become. You can already see one reason why we want a dedicated feature: no normal human being should be forced to go through all these hacks and tricks.

I will skip the explanation of how it works exactly. The point I want to focus on is that this ENABLE_IF placed as additional template parameter can be thought of as saying “this template is invisible when the corresponding condition is not satisfied”. When I have two function templates with the opposite condition in ENABLE_IF , it can be thought of as saying “it is either this declaration or the other, depending on condition”. The two constructors (save for the condition) differ only by keyword explicit , so we could further say, “the constructor is either explicit or converting, depending on the condition.” This being ‘invisible’ is not exactly what we might expect in all circumstances, but is sufficient to guarantee the following:

void test(int i) { // int* -> void* (implicit) Wrapper<void*> w1 = &i; // ok Wrapper<void*> w2 {&i}; // ok // int* -> unique_ptr<int> (explicit) Wrapper<std::unique_ptr<int>> w3 = new int{}; // FAILS Wrapper<std::unique_ptr<int>> w4 {new int{}}; // ok }

Thus, term ‘invisible’ works in the context of overload resolution. It also works in the context of testing “expression validity”:

using VPWrapper = Wrapper<void*>; static_assert( is_convertible<int*, VPWrapper>::value, ""); static_assert( is_constructible<VPWrapper, int*>::value, ""); using UIWrapper = Wrapper<std::unique_ptr<int>>; static_assert(!is_convertible<int*, UIWrapper>::value, ""); static_assert( is_constructible<UIWrapper, int*>::value, "");

Non-templates

enable_if solutions work well with templates, but sometimes we need to make a non-template function disappear. Of course, you never want to constrain a free-standing function:

int fun(int);

You either want it to exist or not: just decide. But the case is different for member functions in class templates. Suppose you want a wrapper class that is copy-constructible if and only if the wrapped type is copy-constructible. In other words, you want to implement a ‘conditional’ copy constructor. This boils down to the following task:

template <typename T> class Wrapper { public: Wrapper (Wrapper const&); //< somehow enable iff // is_copy_constructible<T>::value };

This copy constructor is somehow a ‘templated entity’ but once the class template Wrapper is instantiated, in itself the constructor is a non-template. There is simply no way to employ SFINAE tricks to it, as any substitution has already taken place in the instantiation of class template Wrapper , and all declarations of non-template members have been already instantiated. Declarations — not definitions, so we have a declaration of a copy constructor for any T , no definition yet. It is enough to call a type copy-constructible, but instantiating the body of the copy constructor (when it is used) will trigger a hard compile-time error for non-copyable T s.

This is where we can see the advantage of Concepts Lite constrains: they can be applied not only to templates, but also to non-template members of class templates:

template <typename T> class Wrapper { public: Wrapper (Wrapper const&) requires std::is_copy_constructible<T>::value; };

With this constraint imposed on a non-template function, the ability to disappear goes beyond substitution failure. The constructor declaration is not instantiated when we instantiate the class template.

Emulated non-templates

I was talking about a copy constructor because it is a special member function: it is never a template. If you declare a constructor template with the signature identical to that of the copy constructor, it is still not a copy constructor, and one will be generated by the compiler (even though it might be a worse match than your constructor template). More about this behavior in this post.

However, for any other member function that is not special, we can quite easily turn a non-template member function into a template. Imagine another Wrapper type:

template <typename T> struct Wrapper { T val; explicit Wrapper(T && v) : val(std::move(v)) {} explicit Wrapper(T const & v) // < disable for : val(v) {} // non-copyable types };

This is again a constructor, but this time it is not a special member function, so we can first turn it into a template:

template <typename T> struct Wrapper { template <typename U> explicit Wrapper(U const & v) : val(v) {} };

Now, obviously we allow too much compared to the original, so we have to constrain our template back so that the only allowed U is T :

template <typename T> struct Wrapper { template <typename U, ENABLE_IF(std::is_same<T, U>::value)> explicit Wrapper(U const & v) : val(v) {} };

It looks a bit silly: we have practically arrived at the point we started from, but now we have a constructor template. Constraining it with becomes quite trivial: just add another predicate

template <typename T> struct Wrapper { template <typename U, ENABLE_IF(std::is_same<T, U>::value && std::is_copy_constructible<U>::value)> explicit Wrapper(U const & v) : val(v) {} };

And this practically does the job, convoluted syntax apart; but it has one downside.

Testing class templates

For a normal, non-template, function or class the very basic correctness test, performed at compile-time, is to check whether the piece of code in question even compiles. This checks (1) syntax correctness and (2) type-system correctness. In case of templates only (1) can be achieved immediately when the definition is visible: (2) cannot be performed due to the rules of two-phase name lookup: thus until we know what type our template is going to be instantiated with, we cannot validate the definition’s correctness concerning the type-system: even the obvious blunders. One very easy thing we can do to immediately test the minimum type-system correctness of a class template is to request an explicit class template instantiation: this instantiates definitions of all class’s non-template members. This is different than the implicit instantiation, which only instantiates the member functions’ declarations. This is very attractive: just type one instruction, and all the class’s member functions’ definitions are type-system-tested (for one type T ). But it doesn’t work so well with conditional interfaces. If we go back to the previous definition of our Wrapper :

template <typename T> struct Wrapper { T val; explicit Wrapper(T && v) : val(std::move(v)) {} explicit Wrapper(T const & v) // < not disabled : val(v) {} // in any way };

Even though the second constructor’s definition has a type-system for T = std::unique_ptr<int> , we can safely perfotm an implicit instantiation of Wrapper<std::unique_ptr<int>> , because this only instantiates the constructor declarations, and the declaration alone is fine: you can take a std::unique_ptr<int> by a reference to const:

// ok: implicit instantiation Wrapper<std::unique_ptr<int>> w {std::make_unique<int>(1)};

But the attempt to perform an explicit instantiation ends in failure:

// explicit instantiation: template struct Wrapper<std::unique_ptr<int>>; // FAIL

This fails due to copy construction of T in the second constructor’s body. This problem is observable in std:vector and other STL containers.

Now, if we use the enable_if trick to conditionally disable the second constructor:

template <typename T> struct Wrapper { T val; explicit Wrapper(T && v) : val(std::move(v)) {} template <typename U, ENABLE_IF(std::is_same<T, U>::value && std::is_copy_constructible<U>::value)> explicit Wrapper(U const & v) : val(v) {} };

We will have fixed the compiler error:

// explicit instantiation: template struct Wrapper<std::unique_ptr<int>>; // ok

But we have lost the opportunity to test the body of the second constructor, even if we test with a copy-constructible T :

template struct Wrapper<int>; // ok, but 2nd ctor's body // not instantiated

We get away with the compiler error, because the second constructor is a template, and member templates are simply skipped during the explicit class template instantiation.

Again, non-template member functions constrained with Concepts Lite do not have these problems. Consider the following definition:

template <typename T> struct Wrapper { T val; explicit Wrapper(T && v) : val(std::move(v)) {} explicit Wrapper(T const & v) requires std::is_copy_constructible<T>::value : val(v) {} };

Now, the second constructor is not a template, so its body gets instantiated in:

template struct Wrapper<int>; // ok, both ctors' bodies tested

But because the second constructor’s constraints are not satisfied if T is not copy-constructible. Its instantiation is skipped in:

template struct Wrapper<std::unique_ptr<int>>; // ok

Yes: non-template member functions are not instantiated during the explicit template instantiation when their corresponding constraints are not satisfied. (However, you will not be able to verify it with GCC 6.0 because it did not implement the feature as per Concepts Lite TS.)

Summary

To wrap this up, Concepts Lite constrains are superior to SFINAE-based solutions in two ways. First, they are a dedicated feature providing a clear concise syntax, where intentions are clearly understood both by human programmers and by tools. Second, they work for non-template members of class templates, and unlike enable_if tricks which work by not selecting the overloads during overload resolution, they cause member function declarations never to be emitted in certain class template instantiations.