Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
josuttis_nm_c20_the_complete_guide.pdf
Скачиваний:
53
Добавлен:
27.03.2023
Размер:
5.85 Mб
Скачать

64

Chapter 3: Concepts, Requirements, and Constraints

Benefit From Using Concepts with if constexpr

C++17 introduced a compile-time if that allows us to switch between code depending on certain compiletime conditions.

For example (as introduced before):

template<typename Coll, typename T>

void add(Coll& coll, const T& val) // for floating-point value types

{

if constexpr(std::is_floating_point_v<T>) {

... // special code for floating-point values

}

coll.push_back(val);

}

When generic code must provide different implementations for different kinds of arguments, but the signature is the same, using this approach can be way more readable than providing overloaded or specialized templates.

However, you cannot use if constexpr to provide different APIs, to allow others to add other overloads or specializations later on, or to disable this template in some cases completely. However, remember that you can constrain member functions to enable or disable parts of an API based on requirements.

3.6Afternotes

Since C++98, C++ language designers have been exploring how to constrain the parameters of templates with concepts. There have been multiple approaches for introducing concepts in the C++ programming language (e.g., see http://wg21.link/n1510 by Bjarne Stroustrup). However, the C++ standards committee has not been able to agree on an appropriate mechanism before C++20.

For the C++11 working draft, there was even a very rich concept approach adopted, which was later dropped because it turned out to be too complex. After that, based on http://wg21.link/n3351 a new approach called Concepts Lite was proposed by Andrew Sutton, Bjarne Stroustrup, and Gabriel Dos Reis in http://wg21.link/n3580. As a consequence, a Concepts Technical Specification was opened starting with http://wg21.link/n4549.

Over time, various improvement were made especially according to the experience of implementing the ranges library.

http://wg21.link/p0724r0 proposed to apply the Concepts TS to the C++20 working draft. The finally accepted wording was formulated by Andrew Sutton in http://wg21.link/p0734r0.

Various fixes and improvement were proposed and accepted afterwards. The most visible one was the switch to “standard case” (only lowercase letters and underscores) for the names of the standard concepts as proposed in http://wg21.link/p1754r1.

Chapter 4

Concepts, Requirements, and

Constraints in Detail

This chapter discusses several details of concepts, requirements, and constraints.

In addition, the following chapter lists and discusses all standard concepts that the C++20 standard library provides.

4.1Constraints

To specify requirements for generic parameters you need constraints, which are used at compile time to decide whether to instantiate and compile a template or not.

You can constrain function templates, class templates, variable templates, and alias templates. An ordinary constraint is usually specified by using a requires clause. For example: template<typename T>

void foo(const T& arg) requires MyConcept<T>

...

In front of a template parameter or auto you can also use a concept as a type constraint directly: template<MyConcept T>

void foo(const T& arg)

...

Alternatively:

void foo(const MyConcept auto& arg)

...

65

66

Chapter 4: Concepts, Requirements, and Constraints in Detail

4.2requires Clauses

A requires clause uses the keyword requires with a compile-time Boolean expression to restrict the availability of the template. The Boolean expression can be:

An ad-hoc Boolean compile-time expression

A concept

A requires expression

All constraints can also be used wherever a Boolean expression can be used (especially as an if constexpr condition).

4.2.1 Using && and || in requires Clauses

To combine multiple constraints in requires clauses, we can use the operator &&. For example:

template<typename T>

 

requires (sizeof(T) > 4)

// ad-hoc Boolean expression

&& requires { typename T::value_type; }

// requires expression

&& std::input_iterator<T>

// concept

void foo(T x) {

 

...

 

}

The order of the constraints does not matter.

We can also express “alternative” constraints using the operator ||. For example: template<typename T>

requires std::integral<T> || std::floating_point<T> T power(T b, T p);

Specifying alternative constraints is rarely necessary and should not be done casually because excessive use of the operator || in requires clauses may potentially tax compilation resources (i.e., make compilation noticeably slower).

A single constraint can also involve multiple template parameters. That way, constraints can impose a relationship between multiple types (or values). For example:

template<typename T, typename U> requires std::convertible_to<T, U> auto f(T x, U y) {

...

}

The operators && and || are the only operators you can use to combine multiple constraints without having to use parentheses. For everything else, use parentheses (which formally pass an ad-hoc Boolean expression to the requires clause).