С. Мейерс - Эффективный и современный C++ (1114942), страница 48
Текст из файла (страница 48)
И вновь на выручку приходитстандартная библиотека, предоставляя шаблон std : : decay. Тип std : : decay<T > : : t ypeпредставляет собой то же самое, что и тип Т, но из него удалены все ссылки и квалификаторы const и volat i le. (Я немного вас обманул, потому что std : : decay, кроме того,превращает массивы и типы функций в указатели (см. раздел 1 . 1 ) , но для наших целейможно считать, что std : : decay ведет себя так, как я описал.) Условие, которое должновыполняться для включения рассматриваемого конструктора, имеет вид' std : : is_same<Person, t ypename std: : decay<T > : : type> : : value1 96Гnава 5. Rvalue-ccыnки, семантика перемещений и прямая передачат.е. Person не совпадает с типом Т, без учета всех ссылок и квалификаторов constи volat i l e.
(Как поясняется в разделе 3.3, ключевое слово t ypename перед std: : decayнеобходимо, поскольку тип s t d : : decay<T> : : t ype зависит от параметра шаблона Т. )Вставка этого условия в шаблон std : : enaЫe_if выше, а также форматирование результата для того, чтобы проще понять взаимоотношения между частями кода, дает следующее объявление конструктора с прямой передачей класса Person:class Person {puЫic :template<typename Т,typename = typename s t d : : enaЫe if<! std : : is same<Person ,_typename std: : dесау<Т> : : type>: : value> : : type>explicit Person (T&& n ) ;};Если вы никогда ранее не видели ничего подобного, не пугайтесь. Есть причина,по которой я оставил этот метод напоследок.
Если для того, чтобы избежать смешиванияуниверсальных ссылок и перегрузки вы можете использовать один из прочих методов (аэто почти всегда возможно), вы должны это сделать. Тем не менее, если вы привыкнетек функциональному синтаксису и множеству угловых скобок, это не так плохо. Крометого, это позволяет получить поведение, к которому вы стремитесь. С учетом приведенного выше объявления построение объекта Person из другого объекта Person (lvalue илиrvalue, с квалификатором const или без него, с квалификатором volat i l e или без него)никогда не вызовет конструктор, принимающий универсальную ссылку.Мы добились успеха? Дело сделано?Пока что нет.
Не спешите праздновать. Раздел 5.4 все еще посылает нам свои приветы.Нам надо заткнуть этот фонтан окончательно.Предположим, что класс, производный от Pe rson, реализует операции копированияи перемещения традиционным способом:class SpecialPerson : puЬlic Person {puЫic :SpecialPerson ( const Specia l Pe rson& rhs ) / / Копирующий// конструктор; вызывает конструкторPerson (rhs)/ / базового класса с прямой передачей !}[".SpecialPe rson ( Special Person& & rhs )// ПеремещающийPerson (std: : move (rhs) ) / / конструктор; вызывает конструктор/ / базового класса с прямой передачей !( ... }};S.S.
Знакомство с аnьтернативами переrрузки дnя универсаnьных ссыпок1 97Это тот же код, который вы видели ранее, в конце предыдущего раздела, включая комментарии, увы, оставшиеся справедливыми. Копируя или перемещая объектSpecialPerson, мы ожидаем, что части базового класса будут скопированы или перемещены с помощью копирующего или, соответственно, перемещающего конструктора базового класса. Однако в этих функциях мы передаем объекты Specia lPerson конструкторам базового класса, а поскольку Spe c i a l Person не совпадает с Person (даже послеприменения s t d : : decay}, конструктор с универсальной ссылкой в базовом классе оказывается включенным и без проблем проходит проверку на идеальное совпадение с аргументом Speci a l Person.
Это точное соответствие лучше преобразования производногокласса в базовый, необходимого для связывания объекта Spe c i a l Person с параметромPerson в копирующем и перемещающем конструкторах класса Person, так что при имеющемся коде копирование и перемещение объектов Special Person будет использоватьдля копирования и перемещения частей базового класса конструктор с универсальнойссылкой класса Person! Это чудное ощущение дежавю раздела 5.4 . . .Производный класс просто следует обычным правилам реализации копирующегои перемещающего конструкторов производного класса, поэтому решение этой проблемынаходится в базовом классе и, в частности, в условии, которое контролирует включениеконструктора с универсальной ссылкой класса Person.
Теперь мы понимаем, что надовключать шаблонный конструктор не для любого типа аргумента, отличного от Person,а для любого типа аргумента, отличного как от Person, так и от типа, производногоот P e r s on. Ох уж это наследование!Вас уже не должно удивлять обилие всяческих полезных шаблонов в стандартнойбиблиотеке, так что известие о наличии шаблона, который определяет, является лиодин класс производным от другого, вы должны воспринять с полным спокойствием. Он называется std : : i s_base of. Значение std : : i s_base o f <T l , T2 > : : va lue истинно, если Т2класс, производный от T l .
Пользовательские типы рассматриваютсякак производные от самих себя, так что std : : is _base_o f<T , Т> : : value истинно, еслиТ представляет собой пользовательский тип. (Если Т является встроенным типом,s t d : : is_base_of<T , T> : : va lue ложно.) Это удобно, поскольку мы хотим пересмотретьнаше условие, управляющее отключением конструктора с универсальной ссылкой классаPerson таким образом, чтобы этот конструктор был включен, только если тип Т послеудаления всех ссылок и квалификаторов const и volat i l e не являлся ни типом Person,ни классом, производным от Person. Применение std : : is _base_of вместо std : : is sameдает нам то, что требуется:-_class Person {puЬli c :template<typename Т,typename = typename std : : enaЫe if<! std: : is_Ьase_of<Person,typename std : : decay<T> : : type> : : value198Гnава S . Rvalue-ccыnки, семантика перемещений и прямая передача> : : type>explicit Person (T&& n ) ;);Вот теперь работа завершена.
Вернее, завершена при условии, что мы пишем кодна С++ l l . При использовании С++ 1 4 этот код будет работать, но мы можем использовать псевдонимы шаблонов для std : : еnаЫе if и s t d : : decay, чтобы избавиться от хлама в виде t ypename и : : t уре, получая несколько более приятный код:class Person {1 1 С++ 1 4puЫ i c :template<typename Т,typenamestd : : enaЬle if t<1 1 Меньше кода здесь! std: : is_base_o f<Person,std : : decay_t<T>11 И здесь> : : value>11 И здесь>=explicit Person (T&& n ) ;);Ладно, я признаю: я соврал.
Мы до сих пор не сделали всю работу. Но мы уже близкик завершению. Соблазнительно близки. Честно!Мы видели, как использовать std : : е nаЫе_ i f для выборочного отключения конструктора с универсальной ссылкой класса Person для типов аргументов, которые мы хотимобрабатывать с помощью копирующего и перемещающего конструкторов, но мы еще невидели, как применить его для того, чтобы отличать целочисленные аргументы от не являющихся таковыми. В конце концов, таковой была наша первоначальная цель; проблеманеоднозначности конструктора была всего лишь неприятностью, подхваченной по дороге.Все, что нам нужно сделать (и на этот раз "все" действительно означает, что это ужевсе), - это ( l ) добавить перегрузку конструктора Person для обработки целочисленныхаргументов и (2) сильнее ограничить шаблонный конструктор так, чтобы он был отключен для таких аргументов.
Положите эти ингредиенты в кастрюлю со всем остальным,что мы уже обсуждали, варите на медленном огне и наслаждайтесь ароматом успеха:class Person {puЫ i c :template<typename Т,typenamestd : : enaЬle i f t<! std : : i s_ba se_of<Person, std: : decay_t<T>> : : value=5.5. Знакомство с альтернативами перегрузки для универсальных ссып ок199&&! std: : is integral<std: : remove reference t<Т>> : : value___>>/ / Конструктор для std : : striпg иexplicit Persoп ( T& & п )пame ( s td : : forward<T> ( п ) ) / / аргументов, приводимь� к/ / std : : striпg{}.. .explicit Person (int idx)name (nameFromidx (idx) )1...11 Конструктор для/ / целочисленных аргументов}11 Копирующий и перемещающий конструкторы и т .