Concepts today

I read about concepts a lot before they became available in g++8, but when I started experimenting with them I was surprised the syntax didn't quite work. In practice it works slightly differently when working with g++8 and libstdc++8. There was little information on the internet about that. What can I actually start using these tools, today?

The two obstacles I found so far were that libstdc++8 8.2.1 does not have the header <concepts> yet and that g++ insist you put bool in front of the concept for some reason:

template <typename T>
concept bool is_a = 
   requires(T a) 
   {
      // require that a() can be called on object of type T
      a.a();
   };

I've read that there were different suggestions how the function syntax might work. For example, cppreference.com has examples like this:

template<is_a T>
int f(T a)
{
   return a.a();
}

In g++ you don't need to write like this. Stroustrup's paper titled Concepts: The Future of Generic Programming (http://www.stroustrup.com/good_concepts.pdf) he stated his aim was to make "simple generic code as simple as non-generic code". For this he suggested a short-hand form that, to me, looks very much like g++ will have you write code. I am glad this syntax won in the end. You can now use above concept directly as if it were a type:

int f(is_a a)
{
   return a.a();
}

The other limitation I discovered was that <concepts> isn't yet included with libstc++8. This for example doesn't work, but should:

template <typename T>
concept bool is_a = 
   requires(T a) 
   {
      a.a();
      // require that whatever a.a() returns is convertible to int
      // std::convertible_to Defined in header <concepts>
      requires convertible_to<decltype(a.a()), int>;
   };

This isn't insurmountable problem. Concepts in <concepts> are mostly implemented using tools in <type_traits>. This opens two possibilities. You can use type traits to put together your concepts:

template <typename T>
concept bool is_a = 
   requires(T a) 
   {
      a.a();
      // using <type_traits>
      requires std::is_convertible_v<decltype(a.a()), int>;
   };

Alternatively, you can implement your own tools that should've been in <concepts>. This doesn't need to be as inovative and time consuming as it sounds, becase cppreference.com lists suggested implementations for most tools in <concepts>. I will copy them to my project and use them as if it was <concepts>. The benefit of doing this as opposed to just using type traits is that when <concepts> is finally available, I will be able to just switch the headers and most of the code will stay up-to-date. I did that when stdlib for C++11 didn't ship with make_unique and it was a correct decision.

// will be in <concepts> soon
template <class From, class To>
concept bool convertible_to =
   std::is_convertible_v<From, To> &&
   requires(std::add_rvalue_reference_t<From> (&f)()) {
      static_cast<To>(f());
   };

// my concept
template <typename T>
concept bool is_a = 
   requires(T a) 
   {
      a.a();
      // using own copy of convertible_to
      requires convertible_to<decltype(a.a()), int>;
   };

And the example for same_as

// will be in <concepts> soon
namespace detail {
    template< class T, class U >
    concept bool SameHelper = std::is_same_v<T, U>;
}
 
template< class T, class U >
concept bool same_as = detail::SameHelper<T, U> && detail::SameHelper<U, T>;

// my concept
template <typename T>
concept bool is_a = 
   requires(T a) 
   {
      a.a();
      // require that whatever a.a() returns is same as int
      requires same_as<decltype(a.a()), int>;
   };

The rest of what I tried works pretty much as advertised. Conjunctions and disjunctions work:

template <typename T>
concept bool is_a_and_b = is_a<T> && is_b<T>;

template <typename T>
concept bool is_a_or_b = is_a<T> || is_b<T>;
Asserting on a concept works:
template <typename T>
struct S
{
   static_assert(requires(T a) { a.a(); }, "no a()");
};

It does overload:

int f(is_a a)
{
   return a.a();
}

int f(is_b b)
{
   return b.b();
}

This should be enough syntax to get me started and get some experience with concepts. It's the only way to really learn.


Previous: Map vs unordered_map performance
Next: Multiplatform cmake setup for date-tz