Concepts: Overview

Motivation

  • Error messages on failed template instantiations are brutal

  • ⟶ Solution needed

  • Template metaprogramming is not a solution

    • Few people are able to understand it

    • … let alone read it

  • Enter Concepts

  • Relatively easy language to formulate what’s needed for successful substitution

  • Let’s walk through a simple example: using the std::integral concept from <concepts>

No Problem: Template Instantiation Chosen By Parameter Type

template <typename T>
bool is_even(T num)
{
    return (num % 2) == 0;
}

is_even(3);
is_even(4UL);
is_even(4L);
  • Everything fine

  • Template used with compatible types (compatible?)

  • ⟶ no errors

Problem: Template Instantiation Chosen By Parameter Type

is_even(1.5);
  • Not fine

  • % not defined for floating point numbers

  • Template instantiated nonetheless

  • ⟶ Error (possibly deep down complicated code)

  • ⟶ User can only guess

toolcase-2-conceptless-error.cpp: In instantiation of ‘bool is_even(T) [with T = double]’:
toolcase-2-conceptless-error.cpp:11:25:   required from here
toolcase-2-conceptless-error.cpp:6:17: error: invalid operands of types ‘double’ and ‘int’ to binary ‘operator%’
    6 |     return (num % 2) == 0;
      |            ~~~~~^~~~

Concepts To The Rescue

  • Implicit constraint: % is only defined for integral types

  • Why not make that constraint explicit?

  • A case for <concepts>

  • std::integral

template <typename T>
requires std::integral<T>
bool is_even(T num) { /*...*/ }
  • Gives the following compiler error

  • Have to read carefully though

toolcase-3-concept-integral.cpp:13:25: error: no matching function for call to ‘is_even(double)’
   13 |     std::cout << is_even(1.5) << std::endl;
      |                  ~~~~~~~^~~~~
toolcase-3-concept-integral.cpp:6:6: note: candidate: ‘template<class T>  requires  integral<T> bool is_even(T)’
    6 | bool is_even(T num)
      |      ^~~~~~~
toolcase-3-concept-integral.cpp:6:6: note:   template argument deduction/substitution failed:
toolcase-3-concept-integral.cpp:6:6: note: constraints not satisfied            # <--- HERE!!!
In file included from toolcase-3-concept-integral.cpp:1:
/usr/include/c++/12/concepts: In substitution of ‘template<class T>  requires  integral<T> bool is_even(T) [with T = double]’:
toolcase-3-concept-integral.cpp:13:25:   required from here
/usr/include/c++/12/concepts:100:13:   required for the satisfaction of ‘integral<T>’ [with T = double]
/usr/include/c++/12/concepts:100:24: note: the expression ‘is_integral_v<_Tp> [with _Tp = double]’ evaluated to ‘false’
  100 |     concept integral = is_integral_v<_Tp>;
      |                        ^~~~~~~~~~~~~~~~~~

Syntactic Variations

Apparently C++ does not follow rule 13 of “The Zen of Python”:

There should be one– and preferably only one –obvious way to do
it.
template <typename T>
bool is_even(T num) requires std::integral<T>          // <--- same, but different
{
    return (num % 2) == 0;
}
  • Cool: instead of typename

template <std::integral T>                             // <--- cool
bool is_even(T num)
{
    return (num % 2) == 0;
}
  • Really cool: Abbreviated Function Template

bool is_even(std::integral auto num)                   // <--- cool: abbreviated function template
{                                                      //      (with constraint std::integral)
    return (num % 2) == 0;
}