Function and variable concepts

By , last updated September 3, 2019

In general, there are two kinds of distinct concepts. Those are function concepts and variable concepts. They are similar to their non-concept siblings template functions and template variables. If you haven’t heard about template variables, you’re not alone. Those are new in C++14, and allows you to define a variable as a template. However, variable concepts are modeled around template variables, so they will be covered.

Both function and variable concepts must be declared in a namespace scope. Having concepts within classes, structs or methods is not allowed.

All concepts are implicitly constexpr. Using any specifiers before a concept is considered an error. Recursive concepts is not allowed either, as in a concept can’t refer to itself in the constraints list.

Template variable

As variable concepts are based on template variables, here is a brief introduction.

A template variable is a variable, which accepts a template parameter.

template<typename T>
T variable;

This variable can be used just like any other variable, except you have to specify what type of variable you want to use.

variable<int> = 42;
variable<std::string> = "The answer is 42";

The only difference is that variable<int> and variable<std::string> are two completely different variables where they have their own storage. Modifying one type does not affect any other types.

An other use is with std::map.

template<typename T>
std::map<int, T> tmp_map;

This will create a template variable of the type std::map<int, T>. The second template parameter in a map is the value type.

tmp_map<int>[0] = 42;
tmp_map<std::string>[0] = "The answer is 42";

Using tmp_map with int and std::string will create distinct types, with distinct storage.

Variable concept

A variable concept is similar to a template variable covered above, except a concept is always a compile time type and doesn’t have any runtime cost at all.

In the simplest form a variable concept look like this:

template<typename T> 
concept bool variable_concept = true;

It will accept anything when used. The following methods are all equivalent.

template<typename T>
requires variable_concept<T>
void do_stuff(T vc)
{
    std::cout << vc << "\n";
}

template<variable_concept T>
void do_stuff_2(T vc)
{
    std::cout << vc << "\n";
}

void do_stuff_3(variable_concept vc)
{
    std::cout << vc << "\n";
}

The test code:

int a=42;
std::string b = "The answer is 42";

do_stuff(a);
do_stuff_2(a);
do_stuff_3(a);

do_stuff(b);
do_stuff_2(b);
do_stuff_3(b);

Function concept

A function concept looks and acts like a function. A function concept may look like this.

template<typename T>
concept bool function_concept()
{
    return true;
}

The following methods are all equivalent.

template<typename T>
requires function_concept<T>()
void do_stuff(T fc)
{
    std::cout << fc << "\n";
}

template<function_concept T>
void do_stuff_2(T fc)
{
    std::cout << fc << "\n";
}

void do_stuff_3(function_concept fc)
{
    std::cout << fc << "\n";
}

The only difference in this case from variable concepts, is that the parenthesis in the requires clause, are required when using function concepts. Using the function concept in the template signature (do_stuff_2), or as an implicit template type (do_stuff_3), there are no syntactic differences.

Function and variable concept restrictions

Both function and variable concepts comes with some restrictions.

A variable concept may not:

  • Be declared with any other type than bool.
  • Be declared without an initializer.
  • Have an initializer which is not a constraint expression.

A function concept may not:

  • Be declared with any function specifiers in its declaration.
  • Be declared with any other return type than bool.
  • Have any elements in the parameter list.
  • Have a different function body than { return E; }.
  • Where E is not a constraint expression.

Having a concept type other than bool doesn’t make any sense. A concept will either allow or disallow a constraint expression.

// Invalid variable concept type
template<typename T>
concept int F = true;

// Invalid return type in function concept
template<typename T>
concept int G() { return true; }    

It is not allowed to have a concept declared without a body / definition.

// Invalid empty variable concept
template<typename T>
concept bool F; // error: variable concept has no initializer

// Invalid function concept without function body
template<typename T>
concept bool G(); // error: concept 'G' has no definition

The concept must include a constraint expression.

struct foo {};

// Invalid variable concept without a constraint expression
template<typename T>
concept bool V1 = foo(); // error: initializer is not a constraint-expression

It is not allowed to define concepts without the template<> keyword.

// error: a non-template variable cannot be 'concept'
concept bool VAR = true; 

Explicit specializations of concepts are not allowed. If they were allowed, poking holes in a set of concepts would be very easy to perform. For anyone implementing libraries, this is very much welcome.

template<typename T>
concept bool FN() { return false; }

// error: explicit specialization declared 'concept
template<>
concept bool FN<double>() { return true; }

Professional Software Developer, doing mostly C++. Connect with Kent on Twitter.