Тъй като не бях доволен от това, което открих, се опитах сам да намеря решение и в крайна сметка написах малка библиотека, която позволява формулиране на общи операции върху пакети аргументи. Моето решение има следните характеристики:
- Позволява повторение на всички или някои елементи от пакет аргументи, евентуално посочени чрез изчисляване на техните индекси в пакета;
- Позволява препращане на изчислени части от пакет аргументи към променливи функтори;
- Изисква само включване на един относително кратък заглавен файл;
- Използва широко перфектно препращане, за да позволи тежко вграждане и избягва ненужни копия/премествания, за да позволи минимална загуба на производителност;
- Вътрешното внедряване на итерационните алгоритми разчита на Empty Base Class Optimization за минимизиране на потреблението на памет;
- Лесно е (относително, като се има предвид метапрограмирането на шаблон) за разширяване и адаптиране.
Първо ще покажа какво може да се направи с библиотеката, след което ще публикувам нейното внедряване.
СЛУЧАИ НА УПОТРЕБА
Ето пример за това как функцията for_each_in_arg_pack()
може да се използва за итерация през всички аргументи на пакет и предаване на всеки аргумент във вход към някакъв предоставен от клиента функтор (разбира се, функторът трябва да има общ оператор за извикване, ако пакетът аргументи съдържа стойности на хетерогенни типове):
// Simple functor with a generic call operator that prints its input. This is used by the
// following functors and by some demonstrative test cases in the main() routine.
struct print
{
template<typename T>
void operator () (T&& t)
{
cout << t << endl;
}
};
// This shows how a for_each_*** helper can be used inside a variadic template function
template<typename... Ts>
void print_all(Ts&&... args)
{
for_each_in_arg_pack(print(), forward<Ts>(args)...);
}
Функцорът print
по-горе може да се използва и в по-сложни изчисления. По-конкретно, ето как може да се повтори подмножество (в този случай поддиапазон) на аргументите в пакет:
// Shows how to select portions of an argument pack and
// invoke a functor for each of the selected elements
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(), // The functor to invoke for each element
index_range<0, halfSize>(), // The indices to select
forward<Ts>(args)... // The argument pack
);
cout << "Printing second half:" << endl;
for_each_in_arg_pack_subset(
print(), // The functor to invoke for each element
index_range<halfSize, packSize>(), // The indices to select
forward<Ts>(args)... // The argument pack
);
}
Понякога някой може просто да иска да препрати част от пакет аргументи към някакъв друг променлив функтор, вместо да итерира елементите му и да прехвърли всеки от тях поотделно на непроменлив функтор. Ето какво позволява алгоритъмът forward_subpack()
:
// Functor with variadic call operator that shows the usage of for_each_***
// to print all the arguments of a heterogeneous pack
struct my_func
{
template<typename... Ts>
void operator ()(Ts&&... args)
{
print_all(forward<Ts>(args)...);
}
};
// Shows how to forward only a portion of an argument pack
// to another variadic functor
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()
:
// Shows that arguments in a pack can be indexed
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()
:
// Shows the use of range-based for loops to iterate over a
// homogeneous argument pack
template<typename... Ts>
void print_all(Ts&&... args)
{
static_assert(
is_homogeneous_pack<Ts...>::value,
"Template parameter pack not homogeneous!"
);
for (auto&& x : { args... })
{
// Do something with x...
}
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>
//===============================================================================
// META-FUNCTIONS FOR EXTRACTING THE n-th TYPE OF A PARAMETER PACK
// Declare primary template
template<int I, typename... Ts>
struct nth_type_of
{
};
// Base step
template<typename T, typename... Ts>
struct nth_type_of<0, T, Ts...>
{
using type = T;
};
// Induction step
template<int I, typename T, typename... Ts>
struct nth_type_of<I, T, Ts...>
{
using type = typename nth_type_of<I - 1, Ts...>::type;
};
// Helper meta-function for retrieving the first type in a parameter pack
template<typename... Ts>
struct first_type_of
{
using type = typename nth_type_of<0, Ts...>::type;
};
// Helper meta-function for retrieving the last type in a parameter pack
template<typename... Ts>
struct last_type_of
{
using type = typename nth_type_of<sizeof...(Ts) - 1, Ts...>::type;
};
//===============================================================================
// FUNCTIONS FOR EXTRACTING THE n-th VALUE OF AN ARGUMENT PACK
// Base step
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);
}
// Induction step
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))...));
}
// Helper function for retrieving the first value of an argument pack
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))...));
}
// Helper function for retrieving the last value of an argument pack
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))...));
}
//===============================================================================
// METAFUNCTION FOR COMPUTING THE UNDERLYING TYPE OF HOMOGENEOUS PARAMETER PACKS
// Used as the underlying type of non-homogeneous parameter packs
struct null_type
{
};
// Declare primary template
template<typename... Ts>
struct homogeneous_type;
// Base step
template<typename T>
struct homogeneous_type<T>
{
using type = T;
static const bool isHomogeneous = true;
};
// Induction step
template<typename T, typename... Ts>
struct homogeneous_type<T, Ts...>
{
// The underlying type of the tail of the parameter pack
using type_of_remaining_parameters = typename homogeneous_type<Ts...>::type;
// True if each parameter in the pack has the same type
static const bool isHomogeneous = std::is_same<T, type_of_remaining_parameters>::value;
// If isHomogeneous is "false", the underlying type is the fictitious null_type
using type = typename std::conditional<isHomogeneous, T, null_type>::type;
};
// Meta-function to determine if a parameter pack is homogeneous
template<typename... Ts>
struct is_homogeneous_pack
{
static const bool value = homogeneous_type<Ts...>::isHomogeneous;
};
//===============================================================================
// META-FUNCTIONS FOR CREATING INDEX LISTS
// The structure that encapsulates index lists
template <unsigned... Is>
struct index_list
{
};
// Collects internal details for generating index ranges [MIN, MAX)
namespace detail
{
// Declare primary template for index range builder
template <unsigned MIN, unsigned N, unsigned... Is>
struct range_builder;
// Base step
template <unsigned MIN, unsigned... Is>
struct range_builder<MIN, MIN, Is...>
{
typedef index_list<Is...> type;
};
// Induction step
template <unsigned MIN, unsigned N, unsigned... Is>
struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
{
};
}
// Meta-function that returns a [MIN, MAX) index range
template<unsigned MIN, unsigned MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;
//===============================================================================
// CLASSES AND FUNCTIONS FOR REALIZING LOOPS ON ARGUMENT PACKS
// Implementation inspired by @jogojapan's answer to this question:
// http://stackoverflow.com/questions/14089637/return-several-arguments-for-another-function-by-a-single-function
// Collects internal details for implementing functor invocation
namespace detail
{
// Functor invocation is realized through variadic inheritance.
// The constructor of each base class invokes an input functor.
// An functor invoker for an argument pack has one base class
// for each argument in the pack
// Realizes the invocation of the functor for one parameter
template<unsigned I, typename T>
struct invoker_base
{
template<typename F, typename U>
invoker_base(F&& f, U&& u) { f(u); }
};
// Necessary because a class cannot inherit the same class twice
template<unsigned I, typename T>
struct indexed_type
{
static const unsigned int index = I;
using type = T;
};
// The functor invoker: inherits from a list of base classes.
// The constructor of each of these classes invokes the input
// functor with one of the arguments in the pack.
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))...
{
}
};
}
// The functor provided in the first argument is invoked for each
// argument in the pack whose index is contained in the index list
// specified in the second argument
template<typename F, unsigned... Is, typename... Ts>
void for_each_in_arg_pack_subset(F&& f, index_list<Is...> const& i, Ts&&... args)
{
// Constructors of invoker's sub-objects will invoke the functor.
// Note that argument types must be paired with numbers because the
// implementation is based on inheritance, and one class cannot
// inherit the same base class twice.
detail::invoker<detail::indexed_type<Is, typename nth_type_of<Is, Ts...>::type>...> invoker(
f,
(nth_value_of<Is>(std::forward<Ts>(args)...))...
);
}
// The functor provided in the first argument is invoked for each
// argument in the pack
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)...);
}
// The functor provided in the first argument is given in input the
// arguments in whose index is contained in the index list specified
// as the second argument.
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)...))...);
}
// The functor provided in the first argument is given in input all the
// arguments in the pack.
template<typename F, typename... Ts>
void forward_pack(F&& f, Ts&&... args)
{
f(std::forward<Ts>(args)...);
}
ЗАКЛЮЧЕНИЕ
Разбира се, въпреки че дадох собствен отговор на този въпрос (и всъщност поради този факт), любопитен съм да чуя дали съществуват алтернативни или по-добри решения, които съм пропуснал - освен споменатите в раздела „Свързани произведения“ на въпроса.
person
Andy Prowl
schedule
10.01.2013
foo(args);...
- person Marc Glisse   schedule 10.01.2013Имам UpdateCommand за решетка, както следва:
Когато потребителят въведе дата, трябва да я конвертирам в UTC. Има ли начин да направите това от UpdateCommand - добавете функция. Не се получи, когато го пробвах.
- person Nawaz   schedule 10.01.2013foo(arg)
? също така, какво ще стане, ако типовете връщане наfoo()
са различни за различните типове наarg
? - person Andy Prowl   schedule 10.01.2013foo
, тогава това трябва да е добре:std::tuple<Args...> eat {foo(args)...};
. - person Nawaz   schedule 10.01.2013for_each
, което (в стандартната библиотека) е без индекс, на нещо друго... като изброяване и функторът да приема както елемент, така и индекс (и може би общия размер като добре ?) :) - person Matthieu M.   schedule 13.02.2013