Оскільки я не був задоволений тим, що знайшов, я спробував самостійно розробити рішення і в підсумку написав невелику бібліотеку, яка дозволяє формулювати загальні операції над пакетами аргументів. Моє рішення має такі особливості:
- Дозволяє перебирати всі або деякі елементи пакета аргументів, можливо, вказаних шляхом обчислення їх індексів на пакеті;
- Дозволяє переадресовувати обчислювані частини пакета аргументів до варіативних функторів;
- Потрібно включити лише один порівняно короткий заголовочний файл;
- Широко використовує ідеальну переадресацію, щоб забезпечити важкі вкладання та уникає зайвих копій / переміщень, щоб забезпечити мінімальну втрату продуктивності;
- Внутрішня реалізація ітераційних алгоритмів покладається на Оптимізацію порожнього базового класу для мінімізації споживання пам'яті;
- Його легко (відносно, враховуючи метапрограмування шаблону) розширити та адаптувати.
Спочатку я покажу, що можна зробити з бібліотекою, а потім опублікую її реалізацію .
ВИКОРИСТАННЯ СПРАВ
Ось приклад того, як for_each_in_arg_pack()
функцію можна використовувати для ітерації всіх аргументів пакета та передачі кожного аргументу у вхід до якогось наданого клієнтом функтора (звичайно, функтор повинен мати загальний оператор виклику, якщо пакет аргументів містить значення гетерогенних типів):
struct print
{
template<typename T>
void operator () (T&& t)
{
cout << t << endl;
}
};
template<typename... Ts>
void print_all(Ts&&... args)
{
for_each_in_arg_pack(print(), forward<Ts>(args)...);
}
print
Функтор вище , також може бути використаний в більш складних обчисленнях. Зокрема, ось як можна повторити підмножину (в даному випадку піддіапазон ) аргументів у пакеті:
template<typename... Ts>
void split_and_print(Ts&&... args)
{
constexpr size_t packSize = sizeof...(args);
constexpr size_t halfSize = packSize / 2;
cout << "Printing first half:" << endl;
for_each_in_arg_pack_subset(
print(),
index_range<0, halfSize>(),
forward<Ts>(args)...
);
cout << "Printing second half:" << endl;
for_each_in_arg_pack_subset(
print(),
index_range<halfSize, packSize>(),
forward<Ts>(args)...
);
}
Іноді можна просто захотіти переслати частину пакету аргументів до якогось іншого варіадичного функтора, замість того, щоб перебирати його елементи і передавати кожен з них окремо не варіадичному функтору. Це те, що forward_subpack()
дозволяє робити алгоритм:
struct my_func
{
template<typename... Ts>
void operator ()(Ts&&... args)
{
print_all(forward<Ts>(args)...);
}
};
template<typename... Ts>
void split_and_print(Ts&&... args)
{
constexpr size_t packSize = sizeof...(args);
constexpr size_t halfSize = packSize / 2;
cout << "Printing first half:" << endl;
forward_subpack(my_func(), index_range<0, halfSize>(), forward<Ts>(args)...);
cout << "Printing second half:" << endl;
forward_subpack(my_func(), index_range<halfSize, packSize>(), forward<Ts>(args)...);
}
Для більш конкретних завдань, звичайно, можна отримати конкретні аргументи в пакеті шляхом їх індексації . Це те, що nth_value_of()
дозволяє робити функція, разом із її помічниками first_value_of()
та last_value_of()
:
template<unsigned I, typename... Ts>
void print_first_last_and_indexed(Ts&&... args)
{
cout << "First argument: " << first_value_of(forward<Ts>(args)...) << endl;
cout << "Last argument: " << last_value_of(forward<Ts>(args)...) << endl;
cout << "Argument #" << I << ": " << nth_value_of<I>(forward<Ts>(args)...) << endl;
}
Якщо, з іншого боку, набір аргументів однорідний (тобто всі аргументи мають однаковий тип), переважно може бути така формулювання, як наведена нижче. is_homogeneous_pack<>
Мета-функція дозволяє визначити , чи всі типи в параметрах пакета є однорідними, і в основному призначена для використання в static_assert()
звітності:
template<typename... Ts>
void print_all(Ts&&... args)
{
static_assert(
is_homogeneous_pack<Ts...>::value,
"Template parameter pack not homogeneous!"
);
for (auto&& x : { args... })
{
}
cout << endl;
}
Нарешті, оскільки лямбди - це просто синтаксичний цукор для функторів, їх також можна використовувати в поєднанні з наведеними вище алгоритмами; однак, поки загальні лямбди не будуть підтримуватися C ++, це можливо лише для однорідних пакетів аргументів. Наступний приклад також показує використання homogeneous-type<>
мета-функції, яка повертає тип усіх аргументів в однорідній упаковці:
static_assert(
is_homogeneous_pack<Ts...>::value,
"Template parameter pack not homogeneous!"
);
using type = homogeneous_type<Ts...>::type;
for_each_in_arg_pack([] (type const& x) { cout << x << endl; }, forward<Ts>(args)...);
В основному це те, що дозволяє робити бібліотека, але я вважаю, що її можна навіть розширити для виконання більш складних завдань.
ВПРОВАДЖЕННЯ
Тепер настає реалізація, яка сама по собі трохи хитра, тому я буду покладатися на коментарі, щоб пояснити код і не робити цей пост занадто довгим (можливо, це вже є):
#include <type_traits>
#include <utility>
template<int I, typename... Ts>
struct nth_type_of
{
};
template<typename T, typename... Ts>
struct nth_type_of<0, T, Ts...>
{
using type = T;
};
template<int I, typename T, typename... Ts>
struct nth_type_of<I, T, Ts...>
{
using type = typename nth_type_of<I - 1, Ts...>::type;
};
template<typename... Ts>
struct first_type_of
{
using type = typename nth_type_of<0, Ts...>::type;
};
template<typename... Ts>
struct last_type_of
{
using type = typename nth_type_of<sizeof...(Ts) - 1, Ts...>::type;
};
template<int I, typename T, typename... Ts>
auto nth_value_of(T&& t, Ts&&... args) ->
typename std::enable_if<(I == 0), decltype(std::forward<T>(t))>::type
{
return std::forward<T>(t);
}
template<int I, typename T, typename... Ts>
auto nth_value_of(T&& t, Ts&&... args) ->
typename std::enable_if<(I > 0), decltype(
std::forward<typename nth_type_of<I, T, Ts...>::type>(
std::declval<typename nth_type_of<I, T, Ts...>::type>()
)
)>::type
{
using return_type = typename nth_type_of<I, T, Ts...>::type;
return std::forward<return_type>(nth_value_of<I - 1>((std::forward<Ts>(args))...));
}
template<typename... Ts>
auto first_value_of(Ts&&... args) ->
decltype(
std::forward<typename first_type_of<Ts...>::type>(
std::declval<typename first_type_of<Ts...>::type>()
)
)
{
using return_type = typename first_type_of<Ts...>::type;
return std::forward<return_type>(nth_value_of<0>((std::forward<Ts>(args))...));
}
template<typename... Ts>
auto last_value_of(Ts&&... args) ->
decltype(
std::forward<typename last_type_of<Ts...>::type>(
std::declval<typename last_type_of<Ts...>::type>()
)
)
{
using return_type = typename last_type_of<Ts...>::type;
return std::forward<return_type>(nth_value_of<sizeof...(Ts) - 1>((std::forward<Ts>(args))...));
}
struct null_type
{
};
template<typename... Ts>
struct homogeneous_type;
template<typename T>
struct homogeneous_type<T>
{
using type = T;
static const bool isHomogeneous = true;
};
template<typename T, typename... Ts>
struct homogeneous_type<T, Ts...>
{
using type_of_remaining_parameters = typename homogeneous_type<Ts...>::type;
static const bool isHomogeneous = std::is_same<T, type_of_remaining_parameters>::value;
using type = typename std::conditional<isHomogeneous, T, null_type>::type;
};
template<typename... Ts>
struct is_homogeneous_pack
{
static const bool value = homogeneous_type<Ts...>::isHomogeneous;
};
template <unsigned... Is>
struct index_list
{
};
namespace detail
{
template <unsigned MIN, unsigned N, unsigned... Is>
struct range_builder;
template <unsigned MIN, unsigned... Is>
struct range_builder<MIN, MIN, Is...>
{
typedef index_list<Is...> type;
};
template <unsigned MIN, unsigned N, unsigned... Is>
struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
{
};
}
template<unsigned MIN, unsigned MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;
namespace detail
{
template<unsigned I, typename T>
struct invoker_base
{
template<typename F, typename U>
invoker_base(F&& f, U&& u) { f(u); }
};
template<unsigned I, typename T>
struct indexed_type
{
static const unsigned int index = I;
using type = T;
};
template<typename... Ts>
struct invoker : public invoker_base<Ts::index, typename Ts::type>...
{
template<typename F, typename... Us>
invoker(F&& f, Us&&... args)
:
invoker_base<Ts::index, typename Ts::type>(std::forward<F>(f), std::forward<Us>(args))...
{
}
};
}
template<typename F, unsigned... Is, typename... Ts>
void for_each_in_arg_pack_subset(F&& f, index_list<Is...> const& i, Ts&&... args)
{
detail::invoker<detail::indexed_type<Is, typename nth_type_of<Is, Ts...>::type>...> invoker(
f,
(nth_value_of<Is>(std::forward<Ts>(args)...))...
);
}
template<typename F, typename... Ts>
void for_each_in_arg_pack(F&& f, Ts&&... args)
{
for_each_in_arg_pack_subset(f, index_range<0, sizeof...(Ts)>(), std::forward<Ts>(args)...);
}
template<typename F, unsigned... Is, typename... Ts>
void forward_subpack(F&& f, index_list<Is...> const& i, Ts&&... args)
{
f((nth_value_of<Is>(std::forward<Ts>(args)...))...);
}
template<typename F, typename... Ts>
void forward_pack(F&& f, Ts&&... args)
{
f(std::forward<Ts>(args)...);
}
ВИСНОВОК
Звичайно, незважаючи на те, що я дав власну відповідь на це запитання (і насправді через цей факт), мені цікаво почути, чи існують альтернативні чи кращі рішення, яких я пропустив - крім тих, що згадані в розділі "Пов'язані роботи" питання.
foo(args);...