Добавил:
t.me Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

2 семестр / Литература / Язык программирования С++. Краткий курс. Страуструп

.pdf
Скачиваний:
9
Добавлен:
16.07.2023
Размер:
31.34 Mб
Скачать

7.2.

Концепты

(С++20)

141

Отдавайте

предпочтение

правильно

именованным

понятиям

с

хорошо

определенной семантикой

(§7.2.4)

и

используйте

rеquirеs-выражения

в

их

определениях.

7.2.4.

Оnредеnение

концептов

В

конечном

итоге

мы

ожидаем,

что

сможем

найти

полезные

концепты,

та­

кие

как

Sequence

и

Ari

thmetic,

в

библиотеках

(включая

стандартную

би­

блиотеку).

Техническая

спецификация

[37]

в

настоящее

время

уже предлагает

набор

для

ограничений

алгоритмов

стандартной

библиотеки

(§12.7).

Однако

простые концепты определить несложно.

Концепт -

это предикат времени компиляции,

указывающий,

каким

обра­

зом

можно

использовать

один

или

несколько

типов.

Рассмотрим

сначала

один

из

простейших

примеров:

template<typename Т>

 

 

concept

Equality_comparaЫe

requires

а,

Т

Ь)

{

 

{

а

Ь

}

->

bool;

 

{

а

!= Ь

}

->

bool;

) ;

 

 

 

 

 

 

 

// //

Сравнение Сравнение

Ts

Ts

с с

помощью== помощью !=

Equali

ty

_

comparaЫe

-

это

концепт,

который

мы

используем

для

обеспе­

чения

того,

что

значения

можно

сравнивать

на равенство

и

неравенство.

Мы

просто

говорим,

что

для

двух

значений

данного

типа

они

должны

быть

срав­

ниваемы

с

использованием

операторов

==

и

!

=,

и

результат

этих

операций

должен

быть

преобразуем

в

bool.

Например:

static_assert(Equality_comparaЬle<int>);

//

Усnешно

struct

S

{ int

а;

};

 

 

11 Сбой

 

из-за

отсутствия

у

структур==

static_assert(Equality_comparaЬle<S>);

и

!=по

умолчанию:

Определение

концепта

Equali

ty

_

comparaЫe

в

точности

эквивалентно

описанию

на

естественном языке,

и

не

более

того.

Значение

concept

всегда

имеет тип bool.

 

Определение

Equali ty_ comparaЫe

для

негомогенных

сравнений

прак­

тически

столь

же

простое:

template<typename

Т,

typename Т2

Т>

 

 

 

concept

Equality_comparaЫe

=

 

 

 

 

requires

 

а,

Т2

Ь)

{

11

 

 

 

 

 

{

а

==

ь

}

->

bool;

Сравнение

Т

и

Т2

{

а

!=

ь

}

->

bool;

11

Сравнение

Т

и

Т2

{

ь

==

а

}

->

bool;

11

Сравнение

Т2

и

Т

{

ь

!=

а

}

->

bool;

11

Сравнение

Т2

и

Т

с с с с

помощью помощью помощью помощью

== != == !=

};

146

Глава

7.

Концепты

и

обобщенное

программирование

Традиционно

реализация

вариативного

шаблона

заключалась

в

том,

чтобы

от­

делить первый аргумент от остальных,

а

ный шаблон для остальных аргументов:

 

затем

рекурсивно

вызвать

вариатив­

void

print()

{

 

 

11 Что делать

при

отсутствии

аргументов?

Ничего!

template<typename

Т,

 

typename

...

Tail>

void

print(T head,

Tail ... tail)

 

{

 

 

 

 

 

 

 

 

11 Что

делается

с

каждым

аргументом,

 

cout <<

head <<

'

';

 

 

 

print (tail ... );

 

 

 

 

например

typename ...

указывает,

что

Tail

представляет

собой

последовательность

типов.

Tail

...

указывает,

что

tail

является

последовательностью

значений

типов,

перечисленных

в

Tail.

Параметр,

объявленный с

помощью

троеточия

...

,

называется

пакетом

параметров.

Здесь

tail

-

это

аргумент

функции,

который

представляет

собой

пакет

параметров,

элементы

которого

имеют

типы,

найденные

в

аргументе

шаблона,

который

представляет

собой

пакет

параметров

Tail.

Таким

образом,

print

()

может

принимать

любое

количе­

ство аргументов любых типов.

Вызов print () разделяет аргументы

на

голову

(первый

элемент)

и

хвост

(остальные

элементы).

Выполняется

вывод

головного

элемента,

после

чего

print

()

вызывается

для

хвоста.

В

конечном

итоге

tail

становится

пустым,

поэтому

нам

нужна

версия

pr

in t

()

без аргументов,

способная

справиться

с

этой

ситуацией

и

завершить

рекурсию.

Можно

обойтись

и

без

print

()

без

аргументов,

используя

для

его

исключения

if

времени

компиляции:

template<typename

Т,

 

typename ...

void

print(T

head,

Tail ...

tail)

{

 

 

 

 

 

 

 

cout <<

head <<

'

';

 

 

if constexpr(sizeof ...

(tail)>

 

print (tail ... );

 

Tail>

0)

Я

использовал

if

времени

компиляции

(§6.4.3),

а

не

if

времени выполне­

ния,

чтобы

избежать

генерации

последней,

никогда

не

вызываемой

функции

print ()

без аргументов.

Сила

вариативных шаблонов

заключается

в

том,

что

они

могут

принимать

любые

аргументы,

которые

вы

захотите

им

передать.

Слабые

же

их

стороны

включают

следующее.

7.4.

Вариативные

шаблоны

147

• •

Рекурсивная

реализация может оказаться сложной задачей.

 

Рекурсивные

реализации могут быть неожиданно дорогими

во время

компиляции.

 

 

Проверка типа интерфейса, возможно, представляет собой

сложную

шаблонную программу.

 

Из-за гибкости вариативные шаблоны широко ной библиотеке, иногда даже чрезмерно широко.

используются

в

стандарт­

7.4.1.

Выражения

свертки

Чтобы

упростить

реализацию

простых

вариативных

шаблонов,

С++

17

предлагает

ограниченную

форму

итерации

по

элементам

пакета

параметров.

Например:

template<NumЬer ...

int sum(T ...

v)

Т>

return

(v

+

...

+

0);

//Прибавление

всех

элементов

v

к

О

Здесь

sum

()

может

принимать

любое

количество

элементов

любого

типа.

В

предположении,

что

s

um

( )

действительно

суммирует

свои

аргументы, мы

получаем

int

х

= sum(l,

2,

3,

 

4,

5); //

х

равно

//у

 

равно 114

(2.4

обрезается

до 2,

а

int

у= sum('a',

2.4,

х); равно

 

97)

 

15 значение

символа

'а':

Тело

sum

использует

выражение

свертки:

return

(v

+

...

+

0);

//Прибавление

всех

элементов

v

к

О

Здесь

(

v+

•..

+О)

означает

прибавление

всех

элементов

v

к

начальному

значению

О.

Первым

прибавляется

крайний

справа

аргумент (имеющий

наи­

высший

индекс):

(

v

[О]

+ (v

[ 1 ] + (v

[

2 ]

+ (v

[

3]

+ (v

[

4]

+О}

} } } ) .

То

есть

сум­

мирование

начинается

справа,

где

находится

О.

Такое

действие

называется

правой

сверткой

(right fold).

В

качестве

альтернативы

мы

можем

использо­

вать

левую

свертку

(left fold):

template<NumЬer ...

int sum2 (Т. . .

v)

Т>

return

(0

+

...

+

v);

//Прибавление

всех

элементов

v

к

О

7.5.

Модель

компиляции

шаблонов

149

ром. Различные транспортные конструктора:

механизмы

имеют

разные

наборы

параметров

template<typename Transport>

 

 

requires

concepts::InputTransport<Transport>

class InputChannel

 

{

 

 

 

puЫic:

 

 

 

11 ...

 

 

 

InputChannel(TransportArgs&& ...

transportArgs)

 

transport(std::forward<TransportArgs>(transportArgs)

 

{)

 

 

 

11 . "

transport;

 

 

Transport

 

}

;

 

 

...

)

Функция

стандартной

библиотеки

forward

()

(§13.2.2)

используется

для

пе­

редачи

аргументов

неизменными

из

конструктора

InputChannel

в

конструк­

тор Transport.

 

Дело в том,

что

автор

InputChannel

может

создать

объект

типа

Tran

sport

без

необходимости

знать,

какие

аргументы

необходимы

для

построе­

ния

конкретного

транспорта.

Разработчику

InputChannel

нужно

знать

толь­

ко

общий пользовательский интерфейс для всех объектов Transport. Передача очень распространена в фундаментальных библиотеках,

где

не­

обходимы

общность

и

низкие

накладные

расходы

времени

выполнения,

и

распространены

очень общие

интерфейсы.

7.5.

Модеnь

компиnяции

wабnонов

В

предположении

наличия

концептов

(§7.2),

аргументы

шаблона

проверя­

ются

на

соответствие

его

концептам.

Об

обнаруженных

ошибках

компилятор

сообщает

программисту,

который

должен

решить

указанные проблемы.

Про­

верка

того,

что

в

данный

момент

не

может

быть

проверено,

например

аргу­

менты

для

неограниченных

шаблонных

параметров,

переносится

на

то

время,

когда

будет

сгенерирован

код

для

шаблона

и

его

набора

аргументов

-

"во

время

инстанцирования

шаблона".

В

случае

кода,

разработанного

до

появле­

ния

концептов,

именно

в

этот

момент

и

происходит

проверка

всех

типов.

При

использовании

концептов

эта

проверка

выполняется

только

после того,

как

успешно завершена проверка концептов.

Неприятный побочный эффект (поздней)

проверки

типов

времени

ин­

станцирования

заключается

в

том,

что

ошибка

типа

может

быть

обнаружена

слишком

поздно

и

привести

к

поразительно

плохим

сообщениям

об

ошибках,

поскольку

компилятор

обнаруживает

проблему

только

после

объединения

ин­

формации

из

нескольких

мест

программы.