С++

До и после концептов

Александр Ганюхин

Orion Innovation

2020

Попробуем написать
функцию

Попробуем написать
функцию


							/**
							 * @brief    Производит инкремент аргумента при
							 *           некотором условии
							 */
							template<typename Type>
							void conditionally_increment(Type & arg) {
								if (/* некоторое условие */) {
									++arg;
								}
							}
						

Как написать такую функцию правильно?

Три популярных способа

  • Оставить всё как есть
    
    									template<typename Type>
    									void conditionally_increment(Type & arg);
    								
  • Добавить SFINAE-условие
    
    									template<typename Type, enable_if_t<...> * = nullptr>
    									void conditionally_increment(Type & arg);
    								
  • Добавить интерфейс
    
    									struct IIncrementable;
    									void conditionally_increment(IIncrementable & arg);
    								

Оставим всё как есть

Оставим всё как есть


								template<typename Type>
								void conditionally_increment(Type & arg) {
									if (/* некоторое условие */) {
										++arg;
									}
								}

								int main(){
									struct S {} s;
									conditionally_increment(s);
									return 0;
								}
							

Компилируем...


								<source>: In instantiation of 'void conditionally_increment(Type&) [with Type = main()::S]':
								<source>:7:30:   required from here
								<source>:3:17: error: no match for 'operator++' (operand type is 'main()::S')
								3 |      ++arg;
								  |      ^~~~~
							

Не реалистично...

Будем честными

И добавим немного реализма

Добавим немного реализма


							int main(){
								struct S {} s;
								std::for_each(s, s, [](S){});
								return 0;
							}
						

Компилируем...


								In file included from /opt/wandbox/gcc-head/include/c++/11.0.0/bits/stl_algobase.h:67,
													from /opt/wandbox/gcc-head/include/c++/11.0.0/bits/char_traits.h:39,
													from /opt/wandbox/gcc-head/include/c++/11.0.0/ios:40,
													from /opt/wandbox/gcc-head/include/c++/11.0.0/ostream:38,
													from /opt/wandbox/gcc-head/include/c++/11.0.0/iostream:39,
													from prog.cc:2:
								/opt/wandbox/gcc-head/include/c++/11.0.0/bits/stl_iterator.h:490:5: note: candidate: 'template constexpr bool std::operator==(const std::reverse_iterator<_IteratorL>&, const std::reverse_iterator<_IteratorR>&) requires requires{{std::operator==::__x->base() == std::operator==::__y->base()} -> decltype(auto) [requires std::convertible_to<, bool>];}' (reversed)
									490 |     operator==(const reverse_iterator<_IteratorL>& __x,
										|     ^~~~~~~~
								/opt/wandbox/gcc-head/include/c++/11.0.0/bits/stl_iterator.h:490:5: note:   template argument deduction/substitution failed:
								In file included from /opt/wandbox/gcc-head/include/c++/11.0.0/string:52,
													from /opt/wandbox/gcc-head/include/c++/11.0.0/bits/locale_classes.h:40,
													from /opt/wandbox/gcc-head/include/c++/11.0.0/bits/ios_base.h:41,
													from /opt/wandbox/gcc-head/include/c++/11.0.0/ios:42,
													from /opt/wandbox/gcc-head/include/c++/11.0.0/ostream:38,
													from /opt/wandbox/gcc-head/include/c++/11.0.0/iostream:39,
													from prog.cc:2:
								/opt/wandbox/gcc-head/include/c++/11.0.0/bits/stl_algo.h:3816:22: note:   'main()::S' is not derived from 'const std::reverse_iterator<_IteratorL>'
									3816 |       for (; __first != __last; ++__first)
										 |              ~~~~~~~~^~~~~~~~
								... Еще 800 строчек или более ...
							

Конечно, можно немного улучшить ...


							template<typename Type>
							void conditionally_increment(Type & arg) {
								 // Ошибка компиляции, т.к. Type не имеет operator++
								if (/* некоторое условие */) { ++arg; }
							}
							int main(){
								struct S {} s;
								conditionally_increment(s);
								return 0;
							}
						

								In file included from /opt/wandbox/gcc-head/include/c++/11.0.0/bits/stl_algobase.h:67,
													from /opt/wandbox/gcc-head/include/c++/11.0.0/bits/char_traits.h:39,
													from /opt/wandbox/gcc-head/include/c++/11.0.0/ios:40,
													from /opt/wandbox/gcc-head/include/c++/11.0.0/ostream:38,
													from /opt/wandbox/gcc-head/include/c++/11.0.0/iostream:39,
													from prog.cc:2:
								/opt/wandbox/gcc-head/include/c++/11.0.0/bits/stl_iterator.h:490:5: note: candidate: 'template constexpr bool std::operator==(const std::reverse_iterator<_IteratorL>&, const std::reverse_iterator<_IteratorR>&) requires requires{{std::operator==::__x->base() == std::operator==::__y->base()} -> decltype(auto) [requires std::convertible_to<, bool>];}' (reversed)
									490 |     operator==(const reverse_iterator<_IteratorL>& __x,
										|     ^~~~~~~~
								/opt/wandbox/gcc-head/include/c++/11.0.0/bits/stl_iterator.h:490:5: note:   template argument deduction/substitution failed:
								In file included from /opt/wandbox/gcc-head/include/c++/11.0.0/string:52,
													from /opt/wandbox/gcc-head/include/c++/11.0.0/bits/locale_classes.h:40,
													from /opt/wandbox/gcc-head/include/c++/11.0.0/bits/ios_base.h:41,
													from /opt/wandbox/gcc-head/include/c++/11.0.0/ios:42,
													from /opt/wandbox/gcc-head/include/c++/11.0.0/ostream:38,
													from /opt/wandbox/gcc-head/include/c++/11.0.0/iostream:39,
													from prog.cc:2:
								/opt/wandbox/gcc-head/include/c++/11.0.0/bits/stl_algo.h:3816:22: note:   'main()::S' is not derived from 'const std::reverse_iterator<_IteratorL>'
									3816 |       for (; __first != __last; ++__first)
										 |              ~~~~~~~~^~~~~~~~
								... Еще 800 строчек или более ...
							

Добавим SFINAE-условие

Добавим SFINAE-условие


							template<class, class = void_t<>>
							struct IsInc : false_type {};

							template<class T>
							struct IsInc<T, void_t<decltype( ++declval<T&>() )>>
								: true_type {};

							template<class Ty, enable_if_t<IsInc<Ty>::value> * = nullptr>
							void conditionally_increment(Ty & arg);

							int main(){
								struct S {} s;
								conditionally_increment(s);
								return 0;
							}
						

Компилируем...


								In function 'int main()':
								25:27: error: no matching function for call to 'conditionally_increment(main()::S&)'
									25 |  conditionally_increment(s);
									   |                           ^
								21:6: note: candidate: 'template<class Ty, std::enable_if_t<IsInc<Ty>::value >* <anonymous> > void conditionally_increment(Ty&)'
									21 | void conditionally_increment(Ty & arg);
									   |      ^~~~~~~~~~~~~~~~~~~~~~~
								21:6: note:   template argument deduction/substitution failed:
								In file included from /some/path/bits/move.h:57,
													...
								/some/path/type_traits: In substitution of 'template<bool _Cond, class _Tp> using enable_if_t
									= typename std::enable_if::type [with bool _Cond = false; _Tp = void]':
								20:49:   required from here
								/some/path/type_traits:2557:11: error: no type named 'type' in 'struct std::enable_if<false, void>'
									2557 |     using enable_if_t = typename enable_if<_Cond, _Tp>::type;
										 |           ^~~~~~~~~~~
							

static_assert

Добавим static_assert


							template<typename Type>
							void conditionally_increment(Type & arg) {
								static_assert(IsIncrementable<Type>::value,
									"Provided data type is not incrementable"
								);
								if (/* некоторое условие */) { ++arg; }
							}
							int main(){
								struct S {} s;
								conditionally_increment(s);

								return 0;
							}
						

Компилируем...


								<source>: In instantiation of 'void conditionally_increment(Type&) [with Type = main()::S]':
								<source>:20:30:   required from here
								<source>:15:42: error: static assertion failed: Provided data type is not incrementable
								   15 |     static_assert(IsIncrementable<Type>::value, "Provided data type is not incrementable");
									  |                                          ^~~~~
							

Но...


							template<typename Type>
							void conditionally_increment(Type & arg) {
								static_assert(IsIncrementable<Type>::value, "...");
								if (/* некоторое условие */) { ++arg; }
							}
							template<typename Type>
							void conditionally_increment(Type &) {
								static_assert(!IsIncrementable<Type>::value, "...");
								/* do nothing */
							}
							int main(){
								struct S {} s;
								conditionally_increment(s);

								return 0;
							}
						

Но...


							template<typename Type>
							void conditionally_increment(Type & arg) {
								static_assert(IsIncrementable<Type>::value, "...");
								if (/* некоторое условие */) { ++arg; }
							}
							template<typename Type>
							void conditionally_increment(Type &) {
								static_assert(!IsIncrementable<Type>::value, "...");
								/* do nothing */
							}
							int main(){
								struct S {} s;
								conditionally_increment(s);

								return 0;
							}
						

Компилируем...


								error: redefinition of 'conditionally_increment'
								void conditionally_increment(Type &) {
									 ^
							

Но...


							template<typename Type>
							void conditionally_increment(Type & arg) {
								static_assert(IsIncrementable<Type>::value, "...");
								if (/* некоторое условие */) { ++arg; }
							}
							template<typename Type>
							void conditionally_increment(Type &) {
								static_assert(!IsIncrementable<Type>::value, "...");
								/* do nothing */
							}
							int main(){
								struct S {} s;
								conditionally_increment(s);

								return 0;
							}
						

Выбор...

SFINAE

  • Перегрузка возможна
  • Трудно понять причину ошибки

static_assert

  • Перегрузка невозможна
  • Возможность указать своё сообщение об ошибке

Добавим интерфейс

Добавим интерфейс


							struct IIncrementable {
								virtual ~IIncrementable() = default;
								virtual IIncrementable & operator++() = 0;
							};
							void conditionally_increment(IIncrementable & arg) {
								if (/* некоторое условие */) { ++arg; }
							}

							int main(){
								struct S {} s;
								conditionally_increment(s);

								return 0;
							}
						

Как бы мы описали

RandomAccessIterator?


							class RandomAccessIterator
								: /* ??? */
							{
							};
						

RandomAccessIterator?


							class RandomAccessIterator
								: public IIncrementable
							{
							};
						

RandomAccessIterator?


							class RandomAccessIterator
								: public IIncrementable
								, public IDecrementable
							{
							};
						

RandomAccessIterator?


							class RandomAccessIterator
								: public IIncrementable
								, public IDecrementable
								, public ISubtractive
							{
							};
						

RandomAccessIterator?


							class RandomAccessIterator
								: public IIncrementable
								, public IDecrementable
								, public ISubtractive
								, public IAccessOperator
							{
							};
						

RandomAccessIterator?


							class RandomAccessIterator
								: public IIncrementable
								, public IDecrementable
								, public ISubtractive
								, public IAccessOperator
								, public ICopyConstructible
							{
							};
						

RandomAccessIterator?


							class RandomAccessIterator
								: public IIncrementable
								, public IDecrementable
								, public ISubtractive
								, public IAccessOperator
								, public ICopyConstructible
								, // ...
								, public ILastInterface
							{
							};
						

В итоге

В итоге

<typename T>

  • Легко писать
  • Нет run-time оверхеда
  • Возможно быстрый билд
  • Трудно найти ошибку
  • Невозможность перегрузки

SFINAE

  • Нет run-time оверхеда
  • Возможность перегрузки ИЛИ понятность ошибок
  • Трудно писать
  • Долгий билд

IIncrementable &

  • Легко писать
  • Быстрый билд
  • Легко найти ошибку
  • Возможность перегрузки
  • Адекватность
  • Run-time оверхед
  • Не всегда возможно

А можно чтобы ...

  • ...было легко писать
  • ...работало всегда
  • ...работало со SFINAE
  • ...было легко найти ошибку
  • ...и без виртуальных функций и наследования



Ответ C++20

Концепты!




* если у Вас есть другие идеи или комментарии - заходите общаться после выступления

C++20: Концепты и требования

4-й способ написать функцию

4-й способ написать функцию


							void conditionally_increment(auto & arg) requires requires { ++arg; } {
								if (/* некоторое условие */) { ++arg; }
							}

							int main(){
								struct S {} s;
								conditionally_increment(s);

								return 0;
							}
						

Компилируем...


								...
								<source>:1:6: note: constraints not satisfied
								<source>: In instantiation of 'void conditionally_increment(auto:1&) requires requires{++conditionally_increment::arg;} [with auto:1 = main()::S]':
								<source>:6:30:   required from here
								<source>:1:6:   required by the constraints of 'template<class auto:1> void conditionally_increment(auto:1&) requires requires{++conditionally_increment::arg;}'
								<source>:1:51:   in requirements
								<source>:1:62: note: the required expression '++ arg' is invalid
									1 | void conditionally_increment(auto & arg) requires requires { ++arg; } {
									  |                                                              ^~~~~
							

Давайте разберёмся


							void conditionally_increment(auto & arg)
								requires            // Requires-clause a.k.a. "условие"
								requires { ++arg; } // Requires-expression
							{
								if (/* некоторое условие */) { ++arg; }
							}
							int main(){
								struct S {} s;
								conditionally_increment(s);

								return 0;
							}
						

Requires-clause


							template<typename T>
							void function_name() requires (/* bool constexpr */);
						

Как работает requires-clause

Как работает requires-clause


								// #1
								void foo(auto arg) {
									printf("first");
								}

								// #2
								void foo(auto arg) requires (sizeof(arg) < 2) {
									printf("second");
								}

								foo(10); // first
							
  1. #1 однозначно подходит
  2. Вычисление
    (sizeof(arg) < 2)
  3. (4 < 2) /* == false */
  4. Такая функция игнорируется
    (да, это SFINAE)
  5. Вызывается #1

Как работает requires-clause


								// #1
								void foo(auto arg) {
									printf("first");
								}

								// #2
								void foo(auto arg) requires (sizeof(arg) < 2) {
									printf("second");
								}

								foo('a'); // second
							
  1. #1 однозначно подходит
  2. Вычисление
    (sizeof(arg) < 2)
  3. (1 < 2) /* == true */
  4. Подходят обе функции
  5. В такой ситуации компилятор обязан выбрать наиболее "ограниченную" версию
  6. Вызывается #2

Requires-clause


							template<typename T>
							void function_name() requires (/* bool constexpr */);
						

Requires-clause

для random access iterator


							template<typename T>
							void function_name()
								requires (
									is_dereferencable_v<T> &&
									is_incrementable_v<T>  &&
									is_decrementable_v<T>  &&
									/* ... */
								);
						

Концепт

Концепт - именованый набор требований


								template<typename T>
								void function_name()
									requires (
										is_dereferencable_v<T> &&
										is_incrementable_v<T>  &&
										is_decrementable_v<T>  &&
										/* ... */
									);
							

								template<typename T>
								concept RandomAccessIterator =
									is_dereferencable_v<T> &&
									is_incrementable_v<T>  &&
									is_decrementable_v<T>  &&
									/* ... */;

								template<typename T>
								void function_name()
									requires RandomAccessIterator<T>;
							

Концепт


							template<typename T>
							concept _1ConceptName = /* constraint expression    */;
													/* ограничивающее выражение */

							// функция возможна только в том случае, если
							// constraint expression вычислилось как true
							template<typename T>
							void function_name() requires _1ConceptName<T>;
						

Ограничивающее выражение


							template<typename T>
							concept ConceptName = /* 1-st bool constexpr */ ||
												  /* 2-nd bool constexpr */ &&
												  /* 3-rd bool constexpr ... */;
						
  • Набор выражений времени компиляции, имеющих именно булевый тип данных и соединённых конъюнкцией и/или дизъюнкцией.
  • При этом перегрузки операторов ||, && игнорируются

Особенность #1

Тип именно bool


							template<typename>
							concept Simple = true; // ok
						

Особенность #1

Тип именно bool


							template<typename>
							concept Simple = true; // ok

							template<typename T>
							concept IAmStrange = 0; // error
						

Особенность #1

Тип именно bool


							template<typename>
							concept Simple = true; // ok

							template<typename T>
							concept IAmStrange = 0; // error

							template<typename T>
							concept MakeAnotherTry = 0 || true; // error
						

Особенность #1

Тип именно bool


							template<typename>
							concept Simple = true; // ok

							template<typename T>
							concept IAmStrange = 0; // error

							template<typename T>
							concept MakeAnotherTry = 0 || true; // error

							template<typename T>
							concept IAmStrangeButOk = 0 == 0; // ok
						

Особенность #1

Тип именно bool


							template<typename T>
							concept Integral = std::is_integral_v<T>;

							template<typename T>
							constexpr bool constexpr_val = /* some magic with T */;

							template<typename T>
							concept MyConcept = constexpr_val<T>;

							constexpr bool foo() { /* impl */ };
							template<typename T>
							concept MyConcept2 = foo();
						

Особенность #2

Перегрузки операторов ||, && игнорируются

Особенность #2

Перегрузки операторов ||, && игнорируются

Особенность #3


								template<typename T>
								void function_name() requires (!is_trivial_v<T>);
							

								template<typename T>
								concept ConceptName = !is_trivial_v<T>;

								template<typename T>
								void function_name() requires ConceptName<T>;
							

Выражение концепта через другие концепты


							template<typename>
							concept ConceptA = /* ... */;

							template<typename>
							concept ConceptB = /* ... */;

							template<typename T>
							concept ConceptC = ConceptA<T> && !ConceptB<T>;
						

Выражение концепта через другие концепты


							template<typename>
							concept ConceptA = /* ... */;

							template<typename>
							concept ConceptB = /* ... */;

							template<typename T>
							concept ConceptC = ConceptA<T> && !ConceptB<T>;
						

Requires-выражения


							template<typename>
							concept ConceptA = requires { /* requirements */ };
						

Requires-выражения


									requires {
										/* requirement-seq */
									}
							

								requires ( /* parameters */ ) {
									/* requirement-seq */
								}
							
  • requirement-seq - набор требований
  • parameters - параметры как у функции
    значения по-умолчанию и ellipsis запрещены
  • Возвращает true если все /* requirement-seq */ выполнены

Виды требований


							template<typename Ty>
							concept Name = requires (Ty val, int a, string b) {
								/* requirement-seq */
							};
						

Виды требований


							template<typename Ty>
							concept Name = requires (Ty val, int a, string b) {
								// Простое требование (simple)
								val + a;
							};
						

Виды требований


							template<typename Ty>
							concept Name = requires (Ty val, int a, string b) {
								// Простое требование (simple)
								val + a;

								// Требование типа
								typename Ty::type;
							};
						

Виды требований


							template<typename Ty>
							concept Name = requires (Ty val, int a, string b) {
								// Простое требование (simple)
								val + a;

								// Требование типа
								typename Ty::type;

								// Составные (compound) требования
								{ val + b } noexcept;
							};
						

Виды требований


							template<typename Ty>
							concept Name = requires (Ty val, int a, string b) {
								// Простое требование (simple)
								val + a;

								// Требование типа
								typename Ty::type;

								// Составные (compound) требования
								{ val + b } noexcept;

								// Вложенные (nested) требования
								requires (sizeof(val) > 2);
							};
						

Простые(simple) требования


							template<typename Ty>
							concept Name = requires (Ty val, int a, string b) {
								// Проверяет, что выражения ниже компилируются
								++val;
								val + a;
								b + val;
								b < val;
							};
						

Требования типа


							template<typename Ty>
							concept Name = requires {
								// Проверяет, что выражения ниже компилируются
								// И обозначают тип данных
								typename Ty::pointer;
								typename Ty::reference;
								typename vector<Ty>;
							};
						

Постоянная бдительность #1


Постоянная бдительность #1

Постоянная бдительность #1

Составные (compound) требования


							template<typename Ty>
							concept Name = requires (Ty val, int a) {
								// Проверяет, что выражения ниже компилируются
								// И не бросают исключений
								{ val.~Ty() } noexcept;

								// И возвращаемое значение соответствует концепту
								{ val + a } -> std::same_as<size_t>;
							};
						

Простое


									template<typename Ty>
									concept Name = requires (Ty val, int a) {
										val + a;
									};
								

Составное


									template<typename Ty>
									concept Name = requires (Ty val, int a) {
										{ val + a };
									};
								

Составные (compound) требования


							template<typename Ty>
							concept Name = requires (Ty val, int a) {
								// Проверяет, что выражения ниже компилируются
								// И не бросают исключений
								{ val.~Ty() } noexcept;

								// И возвращаемое значение соответствует концепту
								{ val + a } -> std::same_as<size_t>;
							};
						

Магия стрелочки

Стрелочка "указывает" на концепт,
заполняя первый шаблонный аргумент концепта


							template<typename Ty>
							concept Name = requires (Ty val, int a) {
								{ val + a } -> int; // error: expected concept name with optional arguments
							};
						

Магия стрелочки

Стрелочка "указывает" на концепт,
заполняя первый шаблонный аргумент концепта

Магия стрелочки

Стрелочка "указывает" на концепт,
заполняя первый шаблонный аргумент концепта

Магия стрелочки

Стрелочка "указывает" на концепт,
заполняя первый шаблонный аргумент концепта

Магия стрелочки

Стрелочка "указывает" на концепт,
заполняя первый шаблонный аргумент концепта

Магия стрелочки

Стрелочка "указывает" на концепт,
заполняя первый шаблонный аргумент концепта

Вложенные(nested) требования


							template<typename Ty>
							concept Name = requires {
								// nested
								requires sizeof(Ty) == 1;
								requires ConceptName<Ty>;

								requires requires { Ty{}; }; // ok
								requires { Ty { }; };        // compilation error
							};
						

Постоянная бдительность #2

Постоянная бдительность #2

Обобщим

Обобщим


							consteval bool some_bool_constexpr() { return /* ... */; };
							template<typename T>
							concept ConceptName =
								AnotherConcept <T>      &&
								some_bool_constexpr()   ||
								!requires (T t) { ++t; };
						

Как всё это использовать?

Как всё это использовать?

  • Шаблонные функции
  • Шаблонные классы
  • Везде, где можно употребить auto
  • Шаблонные переменные
  • Шаблонные using

Шаблонные функции


								template<typename>
								concept Integral = /* ... */;

								template<Integral T>
								void foo(T);
							

								template<typename>
								concept Integral = /* ... */;

								template<typename T>
									requires Integral<T>
								void foo(T);
							

								template<typename T>
								concept Integral = /* ... */;

								void foo(Integral auto);
							

								template<typename T>
								concept Integral = /* ... */;
								void foo(auto x) requires Integral<decltype(x)>;
							

Вместо typename


						

После template<typename T>

Вместе с auto


							template<typename T>
							concept Integral = std::is_integral_v<T>;

							template<typename T, size_t Size>
							concept NotMoreThan = sizeof(T) <= Size;

							Integral auto foo(Integral auto x) { return x + 10; }

							NotMoreThan<4> auto x = foo(10);
						

Вместе с auto


										template<typename T>
										concept Integral = std::is_integral_v<T>;

										template<typename T, size_t Size>
										concept NotMoreThan = sizeof(T) <= Size;

										Integral auto foo(Integral auto x) { return x + 10; }

										NotMoreThan<4> auto x = foo(10);
									

										template<typename T>
										concept Integral = std::is_integral_v<T>;

										template<typename T, size_t Size>
										concept NotMoreThan = sizeof(T) <= Size;

										uint64_t foo(Integral auto x) { return x + 10; }

										NotMoreThan<4> auto x = foo(10); // error: deduced type 'unsigned long'
																		 // does not satisfy 'NotMoreThan<4>'
									

Trailing requires-clause


							void foo(auto) { printf("other");      }
							void foo(auto x) requires (sizeof(x) > 4) {
								printf(">4");
							}

							foo(1ull); // integral>4
							foo(1);    // other
						

Trailing requires

Можно использовать ТОЛЬКО если функция шаблонная хотя бы в одном "измерении"

Trailing requires

И даже так!

Шаблонные классы


								template<typename>
								concept Integral = /* ... */;

								template<Integral T>
								class ClassName { };
							

								template<typename>
								concept Integral = /* ... */;

								template<typename T>
									requires Integral<T>
								class ClassName { };
							

Вместо typename


							template<typename T>
							concept LessThanComparable = requires(T t) {
								{ t < t } -> std::convertible_to<bool>;
							};
							template<LessThanComparable T>
							class MySet {
								/* implementation */
							};
						

После template<typename T>


							template<typename T>
							concept EqualityComparable = /* ... */;
							template<typename T>
							concept Hashable = /* ... */;

							template<typename T>
								requires EqualityComparable<T> && Hashable<T>;
							class MyHashSet {
								/* implementation */
							};
						

Шаблонные классы

Частичная специализация

Шаблонные переменные

Аналогично классам

Шаблонные using

Шаблонные using

Для using не существует специализации

Ограниченный using

В качестве выражения

Концепт, при использовании в качестве выражения, всегда имеет булев тип


							template<typename T>
							concept Integral = std::is_integral_v<T>

							template<typename T>
							void foo(T) {
								if constexpr (Integral<T>) {
									/* handle as integral */
								} else {
									/* handle as non-integral */
								}
							}
						

В качестве выражения

Requires-выражение, при использовании в качестве выражения, всегда имеет булев тип


							void foo(auto x) {
								if constexpr (requires { *x; }) {
									cout << *x;
								} else {
									cout << x;
								}
							}

							int i { 10 }, ii { 100 };
							foo(i);   // 10
							foo(&ii); // 100
						

SFINAE-контекст

Однако...

Исправляем

Итак,

Итак, мы узнали, что ...

  • ... в C++20 появились концепты и требования...
  • ... которые можно использовать при написании шаблонных функций, классов, переменных и using ...
  • ... чтобы создавать реализацию для "кластеров" типов

Как концепты меняют C++?

Как менялось
метапрограммирование

~1991-2011

2011-2020

 

2020-∞


							void increment(auto & arg) requires requires { ++arg; };
						

Метапрограммирование
для "простых смертных"

Метапрограммирование
для "простых смертных"

  • Метапрограммирование != template template...
  • Метапрограммирование != непонятно
  • Метапрограммирование != сложно

Метапрограммирование
!=
Метапрограммирование

Мета-else

Мета-else сейчас

Мета-else сейчас

Мета-else

C++17

C++20

Мета-else в C++20

Специализация для метода

Специализация для метода


							template<typename T>
							struct A {
								void foo() requires (sizeof(T) < 4) { printf("<4");  }
								void foo()                          { printf(">=4"); }
							};

							// later
							A<char> a_char;
							A<int>  a_int;

							a_char.foo(); // <4
							a_int.foo();  // >=4
						

Деструктор - тоже метод!


							template<typename T>
							struct OptionalLike {
								aligned_storage</* ... */> storage;

								~OptionalLike() requires (is_trivially_destructible_v<T>) = default;
								~OptionalLike() { destroy(storage); }
							};

							OptionalLike<int>    {}; // will use default, trivial d-tor; line 5
							OptionalLike<string> {}; // will call sting d-tor; line 6
						

Но...


							struct A {
								virtual int foo() = 0;
							};

							template<typename T>
							struct B : public A {
								int foo() requires(sizeof(T) > 1)  { return 10; }
								int foo() requires(sizeof(T) == 1) { return 1; }
							};
						

Компилируем...

Workaround

Всё-таки но...

Компилируем...

:(

Коллизии

Коллизии

Коллизии

Компилируем...

Коллизии

И ещё коллизии

Правильный вариант

Правильный вариант

#1

#2

Variadic templates

Variadic templates

Компилируем...

И даже folding expressions!

И даже folding expressions!

Компилируем...

Обновлённые лямбда-выражения


							// Простейшая версия
							auto lambda = []<typename>(){};

							// Общий вид
							auto l = []<typename T> requires true
									 (T const & arg) constexpr noexcept
									 -> Concept auto requires (sizeof(T) > 0)
									 { return 0; };
						

Лямбда-выражения

Компилируем...

Подведем итог

Подведем итог

  • Появился ПЕРВЫЙ инструмент ТОЛЬКО для метапрограммирования
  • Появился крайне удобный заменитель enable_if...
    • ...который позволяет писать мета-else...
    • ... но следует остерегаться коллизий
  • Появилась возможность специализации конкретного метода...
    • ... но есть некоторые особенности...
    • ... не работает с виртуальными деструкторами
  • Полная интеграция с существующими инструментами
  • Будьте внимательны при написании requires-expression
  • Будьте внимательны при написании requires-expression
    (впрочем, как и всегда в C++)

В сухом остатке

В сухом остатке

Концепты - колоссальное упрощение метапрограммирования


  • Меньше потенциальных ошибок
  • Проще поддерживать код
  • Более быстрое обучение кадров
  • Сокращение времени разработки

Используйте концепты

и будьте бдительны!

Хотите проверить себя?


Успейте до 15.11.2020


10 самых правильных и быстрых получат призы

Удачи!