Давеча упоролся шаблонами настолько, что потребовалось сделать на C++ что-то вот такое (проще сперва показать на примере, а уже потом рассказывать словами):
template< typename First_base, typename Second_base > class Some_complex_template : public First_base // Boom #1 , public Second_base // Boom #2 { public : // Constructor. template< typename... First_base_args, typename... Second_base_args > Some_complex_template( First_base_args &&...first_args, // Args for the first base class. Second_base_args &&...second_args ) // Args for the second base class. : First_base{ std::forward<First_base_args>(first_args)... } , Second_base{ std::forward<Second_base_args>(second_args)... } {} ... }; |
Т.е. нужно было отнаследовать шаблонный класс Some_complex_template от двух других классов, которые задаются параметрами шаблона. А затем в конструктор Some_complex_template нужно было передать два независимых друг от друга набора параметров. Первый набор параметров должен уйти в конструктор первого базового класса, второй набор -- в конструктор второго базового класса.
Насколько я понимаю, C++ не позволяет написать фукнцию/метод с переменным количеством параметров вот так: f(First &&...first, Second &&...second), что логично, т.к. при вызове f(a1, a2, a3, a4, a5) невозможно понять, что из a(i) относится к first, а что к second.
Поэтому выход из ситуации сейчас ищется в использовании std::tuple вот в таком сценарии:
template< typename First_base, typename Second_base > class Some_complex_template : public First_base , public Second_base { public : // Constructor. template< typename... First_base_args, typename... Second_base_args > Some_complex_template( std::tuple<First_base_args...> && first_args, // Args for the first base class. std::tuple<Second_base_args...> && second_args ) // Args for the second base class. : First_base{ /*some magic is required here!*/(first_args)... } , Second_base{ /*some magic is required here!*/(second_args)... } {} ... }; |
Но вот тут возникает вопрос, как же распаковать содержимое std::tuple в вызов конструктора базового типа?
В принципе, вся эта магия с распаковкой std::tuple в вызов некоторой функции f хорошо проиллюстрирована на том же cppreferece.com в описании возможной реализации функции std::apply. Но есть нюанс: нам нужно сделать вызов конструктора базового типа, поэтому у нас нет возможности заводить вспомогательные функции, вроде apply_impl, в которые передается дополнительный аргумент std::index_sequence...
Или все-таки есть?
Все-таки есть :)
Мы легко можем воспользоваться делегирующими конструктором. Вот, для простоты иллюстрации пример для всего одного базового типа:
#include <tuple> #include <utility> #include <cassert> struct A { int a_; float b_; A(int a, float b) : a_(a), b_(b) {} }; template< typename Base > struct D : public Base { template< typename... Items > D( int c, std::tuple<Items...> items ) : D{ c, std::move(items), std::make_index_sequence<sizeof...(Items)>{} } {} template< typename Tuple, std::size_t... Indexes > D(int c, Tuple && items, std::index_sequence<Indexes...>) : Base{ std::get<Indexes>(std::forward<Tuple>(items))... } , c_{c} {} int c_; }; int main() { D<A> d(0, std::make_tuple(1, 0.2f)); assert( 0 == d.c_ ); assert( 1 == d.a_ ); } |
Здесь фокус в том, что у структуры D есть два конструктора. Первый -- нормальный, который должен использоваться пользователем. Первый конструктор получает std::tuple. А вот второй конструктор вспомогательный. По хорошему, он не должен быть виден снаружи. И как раз второй конструктор уже получает полный набор аргументов для того, чтобы выполнить магию с std::get. Первый же конструктор просто делегирует всю работу второму конструктору.
Пример должен быть компилябельным, можно скопипастить и проверить его на каком-нибудь вменяемом C++14 компиляторе (я пробовал под gcc-5.2, clang-3.9, vc++14 и 15).
Такой же фокус можно отмасштабировать и на случай с двумя базовыми классами. Только кода получается заметно больше, а так-то принцип точно такой же:
#include <tuple> #include <utility> #include <cassert> struct A { int a_; float b_; A(int a, float b) : a_{a}, b_{b} {} }; struct B0 { B0() {} }; struct B1 { long b1_; B1(long b1) : b1_{b1} {} }; template< typename First_base, typename Second_base > struct D : public First_base , public Second_base { template< typename... First_pack, typename... Second_pack > D(int c, std::tuple<First_pack...> && first_pack, std::tuple<Second_pack...> && second_pack ) : D{ c, std::move(first_pack), std::make_index_sequence<sizeof...(First_pack)>{}, std::move(second_pack), std::make_index_sequence<sizeof...(Second_pack)>{} } {} template< typename First_tuple, std::size_t... First_tuple_indexes, typename Second_tuple, std::size_t... Second_tuple_indexes > D(int c, First_tuple && first_pack, std::index_sequence<First_tuple_indexes...>, Second_tuple && second_pack, std::index_sequence<Second_tuple_indexes...> ) : First_base{ std::get<First_tuple_indexes>( std::forward<First_tuple>(first_pack))... } , Second_base{ std::get<Second_tuple_indexes>( std::forward<Second_tuple>(second_pack))... } , c_{c} {} int c_; }; int main() { D<A, B0> d(0, std::make_tuple(2, 0.2f), std::make_tuple()); assert( sizeof(A) + sizeof(int) == sizeof(d) ); assert( 0 == d.c_ ); assert( 2 == d.a_ ); D<A, B1> d1(3, std::make_tuple(4, 0.2f), std::make_tuple(5)); assert( sizeof(A) + sizeof(int) + sizeof(long) == sizeof(d1) ); assert( 3 == d1.c_ ); assert( 4 == d1.a_ ); assert( 5 == d1.b1_ ); } |
Ну вот как-то так. Упарываться шаблонами, так упарываться :)
Комментариев нет:
Отправить комментарий