Инстанциирование шаблонов функций по списку типов (Часть 2)

В первой части мы обсудили, как добиться переноса определения шаблона фунции из заголовочного файла в исходник, если набор типов, для которых должен быть инстанциирован шаблон известен заранее. В этой части мы посмотрим, как добиться этого красиво.В первую очередь прийдётся сделать то, что я планировал сделать ближе к концу, но судя по комментариям из первой части, это нужно сделать срочно. А именно, больше никаких списков типов по Александреску. Теперь только шаблоны с переменным количеством параметров, или вариадики, в народе: template struct TypeList{}; // пустой класс для упаковки наборов типов typedef TypeList MyTypeList; // конкретный набор типов для инстанциирования шаблона функции Вернёмся к последнему результату из первой части и перепишем его на манер вариадиков: template struct SomethingDoer { static void doSomething () { } }; template struct SomethingDoer> { typedef void (MyClass::*MemFuncType)(Head); static void doSomething () { volatile MemFuncType x = &MyClass: f; (void)(x); SomethingDoer>:: doSomething (); } }; template struct Instantiator { Instantiator () { SomethingDoer:: doSomething (); } }; Instantiator a; Теперь давайте подумаем, как усовершенствовать это решение. Совершенствование необходимо в двух направлениях: обобщение и избавление от хлама.Со вторым заданием, вроде проще, разобраться. Весь класс Instantiator только и создан для того, чтобы слелать вызов: SomethingDoer:: doSomething ();. Ну, так это можно сделать и проще: template struct SomethingDoer { static int doSomething () // теперь doSomething возвращает значение, а это значит, что её результат можно присвоить переменной. { return 0; } }; template struct SomethingDoer> { typedef void (MyClass::*MemFuncType)(Head); static int doSomething () { volatile MemFuncType x = &MyClass: f; (***) (void)(x); return SomethingDoer>:: doSomething (); } }; int a = SomethingDoer:: doSomething (); // тоже самое, что и раньше, только немного короче. Ну, а теперь давайте обобщать. Самое больное место отмечено (***). Здесь происходит инстанциирования конкретного шаблона метода конкретного класса.Весь остальной код ни с этим шаблоном, ни с классом ничего общего не имеет. Значит нужно вынести остаток кода за скобки, а в строчку с (***) вставить что-то более обобщённое. Т.е, если мы засунем шаблон MyClass: f в какую-нибудь оболочку, типа template struct FuncWrapper_MyClass_f { typedef decltype (&MyClass: f) type; static constexpr type value = &MyClass: f; }; , то внутри функции doSomething нам уже ни класс MyClass, ни его шаблон метода f () не понадобятся.Вот как теперь можно записать (класс SomethingDoer теперь переименован в Instantiator, а метод doSomething () — в instantiate ()). template // пока всё, как и раньше struct Instantiator> // пока всё, как и раньше { typedef Instantiator > TailInstantiator; // тип для рекурсивного инстанциатора для хвоста списка типов static std: pair:: type, decltype (TailInstantiator: instantiate ())> instantiate () { auto headTypeInstantiate = FuncWrapper_MyClass_f:: value; // здесь шаблон инстанциируится для головы списка, return std: make_pair (headTypeInstantiate, TailInstantiator: instantiate ()); //, а здесь рекурсивно запускается инстанциирование для хвоста списка. } }; Не обращайте внимания на всякие навороты с возвратом пары — это нужно, чтобы компилятор не попытался отлынить от части своей работы. Теперь осталось только заменить упаковочный класс FuncWrapper_MyClass_f на что-то более общее. Всё, что нам нужно внутри метода instantiate () — это аттрибут FuncWrapper_MyClass_f: value и тип FuncWrapper_MyClass_f: type. Выглядеть это будет так: templateclass FuncWrapperType, typename Type> // первый параметр шаблона — и есть тот новый обобщённый FuncWrapper_MyClass_f struct Instantiator { static int instantiate (…) { return 0; } };

templateclass FuncWrapperType, typename Head, typename… Tail> struct Instantiator > { typedef Instantiator > TailInstantiator; static typename std: pair:: type, decltype (TailInstantiator: instantiate ())> instantiate () { auto headTypeInstantiate = FuncWrapperType:: value; return std: make_pair (headTypeInstantiate, TailInstantiator: instantiate ()); } }; auto a = Instantiator:: instantiate (); // всё, как раньше, только теперь указываем FuncWrapper_MyClass_f снаружи instantiate (), а не внутри. Данный шаг был самым важным из всех предыдущих. Нам удалось сделать механизм инстанциирования независимым от конкретных шаблонов, для которых этот механизм должен быть применён. Весь код struct Instantiator стал общим и теперь может быть вынесен из исходника myclass.cpp и быть использован для инстанциирования шаблонов других функций или методов класса. Всё что остаётся прописать в MyClass.cpp — это упаковочку FuncWrapper_MyClass_f. Но и это дело неблагородное, и если у Вас нету религиозных соображений на тему макросов препроцессора, то давайте для чёрной работёнки ими и воспользуемся. define InstantiateMemFunc (className, funcName, types) \ template \ struct FuncWrapper_##className_##funcName \ { \ typedef decltype (&className: funcName) type; \ static constexpr type value = &className: funcName; \ }; \ volatile auto i_##className_##funcName = detail: Instantiator:: instantiate (); Этот код тоже универсален и может быть помещён в центральном месте. В MyClass.cpp остаётся лишь написать: namespace { InstantiateMemFunc (MyClass, f, MyTypeList) } И всё. Также можно написать очень похожий макрос для свободных функций, а не методов класса, но не хочется место переводить.Подведём итоги. В этой статье мы рассмотрели, как можно избавиться от определеия шаблонов в заголовочном файле при определённых условиях. Не ахти какая проблема, конечно, но всё-таки не аккуратненько. Заголовочный файл — это лицо программиста, а исходник — это его опа. На опе волосы могут быть, а на лице, кроме админов, раввинов и, разумеется, админ-раввинов, — нет. Мы, С++ программисты, должны следить за своим лицом, иначе оно превратится в…

© Habrahabr.ru