传递许多函数并将它们的所有结果存储在一个元组中
Passing many functions and storing all their results in a tuple
考虑这个输出:
int foo (int, char) {std::cout << "foo\n"; return 0;}
double bar (bool, double, long ) {std::cout << "bar\n"; return 3.5;}
bool baz (char, short, float) {std::cout << "baz\n"; return true;}
int main() {
const auto tuple = std::make_tuple(5, 'a', true, 3.5, 1000, 't', 2, 5.8);
multiFunction<2,3,3> (tuple, foo, bar, baz); // foo bar baz
}
所以multiFunction<2,3,3>
取tuple
的前2个元素传递给foo
,tuple
的后3个元素传递给bar
,等等......我得到了这个工作(除非函数有重载,这是一个单独的问题)。但是每个调用的函数的 return 值都丢失了。我希望将这些 return 值存储在某处,例如
std::tuple<int, double, bool> result = multiFunction<2,3,3> (tuple, foo, bar, baz);
但我不知道如何实现。对于那些想要帮助完成这项工作的人,这里是我目前的(更新的)工作代码,它只将输出存储到一个字符串流中。取回所有值并不容易,特别是如果流中保存的对象很复杂 类.
#include <iostream>
#include <tuple>
#include <utility>
#include <sstream>
template <std::size_t N, typename Tuple>
struct TupleHead {
static auto get (const Tuple& tuple) { // The subtuple from the first N components of tuple.
return std::tuple_cat (TupleHead<N-1, Tuple>::get(tuple), std::make_tuple(std::get<N-1>(tuple)));
}
};
template <typename Tuple>
struct TupleHead<0, Tuple> {
static auto get (const Tuple&) { return std::tuple<>{}; }
};
template <std::size_t N, typename Tuple>
struct TupleTail {
static auto get (const Tuple& tuple) { // The subtuple from the last N components of tuple.
return std::tuple_cat (std::make_tuple(std::get<std::tuple_size<Tuple>::value - N>(tuple)), TupleTail<N-1, Tuple>::get(tuple));
}
};
template <typename Tuple>
struct TupleTail<0, Tuple> {
static auto get (const Tuple&) { return std::tuple<>{}; }
};
template <typename Tuple, typename F, std::size_t... Is>
auto functionOnTupleHelper (const Tuple& tuple, F f, const std::index_sequence<Is...>&) {
return f(std::get<Is>(tuple)...);
}
template <typename Tuple, typename F>
auto functionOnTuple (const Tuple& tuple, F f) {
return functionOnTupleHelper (tuple, f, std::make_index_sequence<std::tuple_size<Tuple>::value>{});
}
template <typename Tuple, typename... Functions> struct MultiFunction;
template <typename Tuple, typename F, typename... Fs>
struct MultiFunction<Tuple, F, Fs...> {
template <std::size_t I, std::size_t... Is>
static inline auto execute (const Tuple& tuple, std::ostringstream& oss, const std::index_sequence<I, Is...>&, F f, Fs... fs) {
const auto headTuple = TupleHead<I, Tuple>::get(tuple);
const auto tailTuple = TupleTail<std::tuple_size<Tuple>::value - I, Tuple>::get(tuple);
// functionOnTuple (headTuple, f); // Always works, though return type is lost.
oss << std::boolalpha << functionOnTuple (headTuple, f) << '\n'; // What about return types that are void???
return MultiFunction<std::remove_const_t<decltype(tailTuple)>, Fs...>::execute (tailTuple, oss, std::index_sequence<Is...>{}, fs...);
}
};
template <>
struct MultiFunction<std::tuple<>> {
static auto execute (const std::tuple<>&, std::ostringstream& oss, std::index_sequence<>) { // End of recursion.
std::cout << std::boolalpha << oss.str();
// Convert 'oss' into the desired tuple? But how?
return std::tuple<int, double, bool>(); // This line is just to make the test compile.
}
};
template <std::size_t... Is, typename Tuple, typename... Fs>
auto multiFunction (const Tuple& tuple, Fs... fs) {
std::ostringstream oss;
return MultiFunction<Tuple, Fs...>::execute (tuple, oss, std::index_sequence<Is...>{}, fs...);
}
// Testing
template <typename T> int foo (int, char) {std::cout << "foo<T>\n"; return 0;}
double bar (bool, double, long ) {std::cout << "bar\n"; return 3.5;}
template <int...> bool baz (char, short, float) {std::cout << "baz<int...>\n"; return true;}
int main() {
const auto tuple = std::make_tuple(5, 'a', true, 3.5, 1000, 't', 2, 5.8);
std::tuple<int, double, bool> result = multiFunction<2,3,3> (tuple, foo<bool>, bar, baz<2,5,1>); // foo<T> bar baz<int...>
}
这是一种贪婪地推导参数数量的方法:
#include <tuple>
namespace detail {
using namespace std;
template <size_t, size_t... Is, typename Arg>
constexpr auto call(index_sequence<Is...>, Arg&&) {return tuple<>{};}
template <size_t offset, size_t... Is, typename ArgT, typename... Fs>
constexpr auto call(index_sequence<Is...>, ArgT&&, Fs&&...);
template <size_t offset, size_t... Is,
typename ArgT, typename F, typename... Fs,
typename=decltype(declval<F>()(get<offset+Is>(declval<ArgT>())...))>
constexpr auto call(index_sequence<Is...>, ArgT&& argt, F&& f, Fs&&... fs) {
return tuple_cat(make_tuple(f(get<offset+I>(forward<ArgT>(argt))...)),
call<offset+sizeof...(Is)>(index_sequence<>{},
forward<ArgT>(argt),
forward<Fs>(fs)...));}
template <size_t offset, size_t... Is, typename ArgT, typename... Fs>
constexpr auto call(index_sequence<Is...>, ArgT&& argt, Fs&&... fs) {
return call<offset>(index_sequence<Is..., sizeof...(Is)>{},
forward<ArgT>(argt), forward<Fs>(fs)...);}
}
template <typename ArgT, typename... Fs>
constexpr auto multifunction(ArgT&& argt, Fs&&... fs) {
return detail::call<0>(std::index_sequence<>{},
std::forward<ArgT>(argt), std::forward<Fs>(fs)...);}
Demo。但是,上面的 return 值的数量具有二次时间复杂度,因为 tuple_cat
是递归调用的。相反,我们可以使用略微修改的 call
版本来获取每次调用的索引 - 然后直接获取实际的 tuple
:
#include <tuple>
namespace detail {
using namespace std;
template <size_t, size_t... Is, typename Arg>
constexpr auto indices(index_sequence<Is...>, Arg&&) {return tuple<>{};}
template <size_t offset, size_t... Is, typename ArgT, typename... Fs>
constexpr auto indices(index_sequence<Is...>, ArgT&&, Fs&&...);
template <size_t offset, size_t... Is, typename ArgT, typename F, class... Fs,
typename=decltype(declval<F>()(get<offset+Is>(declval<ArgT>())...))>
constexpr auto indices(index_sequence<Is...>, ArgT&& argt, F&& f, Fs&&... fs){
return tuple_cat(make_tuple(index_sequence<offset+Is...>{}),
indices<offset+sizeof...(Is)>(index_sequence<>{},
forward<ArgT>(argt),
forward<Fs>(fs)...));}
template <size_t offset, size_t... Is, typename ArgT, typename... Fs>
constexpr auto indices(index_sequence<Is...>, ArgT&& argt, Fs&&... fs) {
return indices<offset>(index_sequence<Is..., sizeof...(Is)>{},
forward<ArgT>(argt), forward<Fs>(fs)...);}
template <typename Arg, typename F, size_t... Is>
constexpr auto apply(Arg&& a, F&& f, index_sequence<Is...>) {
return f(get<Is>(a)...);}
template <typename ITuple, typename Args, size_t... Is, typename... Fs>
constexpr auto apply_all(Args&& args, index_sequence<Is...>, Fs&&... fs) {
return make_tuple(apply(forward<Args>(args), forward<Fs>(fs),
tuple_element_t<Is, ITuple>{})...);
}
}
template <typename ArgT, typename... Fs>
constexpr auto multifunction(ArgT&& argt, Fs&&... fs) {
return detail::apply_all<decltype(detail::indices<0>(std::index_sequence<>{},
std::forward<ArgT>(argt),
std::forward<Fs>(fs)...))>
(std::forward<ArgT>(argt), std::index_sequence_for<Fs...>{},
std::forward<Fs>(fs)...);}
从头开始构建并忽略完美转发,这样我就可以减少输入。我们需要几个帮手。首先,我们需要一个 apply 的部分版本,它从我们想要应用到函数的元组中获取哪些索引:
<class Tuple, class F, size_t... Is>
auto partial_apply(Tuple tuple, F f, std::index_sequence<Is...>) {
return f(get<Is>(tuple)...);
}
然后,我们需要为元组的每个子集调用该函数。假设我们已经将所有函数和索引包装在一个元组中:
template <class Tuple, class FsTuple, class IsTuple, size_t... Is>
auto multi_apply(Tuple tuple, FsTuple fs, IsTuple indexes, std::index_sequence<Is...>) {
return std::make_tuple(
partial_apply(tuple,
std::get<Is>(fs),
std::get<Is>(indexes)
)...
);
}
所以在这种情况下,我们希望最终调用 multi_apply(tuple, <foo,bar,baz>, <<0,1>,<2,3,4>,<5,6,7>>, <0, 1, 2>)
。
我们只需要构建 indexes
部分。我们从 <2,3,3>
开始。我们需要获得部分和 (<0,2,5>
) 并将其添加到索引序列 <<0,1>,<0,1,2>,<0,1,2>>
。所以我们可以写一个偏和函数:
template <size_t I>
using size_t_ = std::integral_constant<size_t, I>;
template <class R, size_t N>
R partial_sum_(std::index_sequence<>, R, size_t_<N> ) {
return R{};
}
template <size_t I, size_t... Is, size_t... Js, size_t S>
auto partial_sum_(std::index_sequence<I, Is...>,
std::index_sequence<Js...>, size_t_<S> )
{
return partial_sum_(std::index_sequence<Is...>{},
std::index_sequence<Js..., S>{}, size_t_<S+I>{} );
}
template <size_t... Is>
auto partial_sum_(std::index_sequence<Is...> is)
{
return partial_sum_(is, std::index_sequence<>{}, size_t_<0>{} );
};
我们可以使用它来生成我们所有的索引作为一个元组:
template <size_t... Is, size_t N>
auto increment(std::index_sequence<Is...>, size_t_<N> )
{
return std::index_sequence<Is+N...>{};
}
template <class... Seqs, size_t... Ns>
auto make_all_indexes(std::index_sequence<Ns...>, Seqs... seqs)
{
return std::make_tuple(increment(seqs, size_t_<Ns>{})...);
}
像这样:
template <size_t... Is, class Tuple, class... Fs>
auto multiFunction(Tuple tuple, Fs... fs)
{
static_assert(sizeof...(Is) == sizeof...(Fs));
return multi_apply(tuple,
std::make_tuple(fs...),
make_all_indexes(
partial_sum_(std::index_sequence<Is...>{}),
std::make_index_sequence<Is>{}...
),
std::make_index_sequence<sizeof...(Is)>{}
);
}
如果你想处理void
returns,那么就把partial_apply
return做成一个单一元素的元组(或者一个空元组),然后改变make_tuple()
在 multi_apply
到 tuple_cat()
中的用法。
还有一个例子:
template<std::size_t N>
constexpr Array<std::size_t, N> scan(std::size_t const (&a)[N])
{
Array<std::size_t, N> b{};
for (int i = 0; i != N - 1; ++i)
b[i + 1] = a[i] + b[i];
return b;
}
template<std::size_t O, std::size_t... N, class F, class Tuple>
inline decltype(auto) eval_from(std::index_sequence<N...>, F f, Tuple&& t)
{
return f(std::get<N + O>(std::forward<Tuple>(t))...);
}
template<std::size_t... O, std::size_t... N, class Tuple, class... F>
inline auto multi_function_impl1(std::index_sequence<O...>, std::index_sequence<N...>, Tuple&& t, F... f)
{
return pack(eval_from<O>(std::make_index_sequence<N>(), f, std::forward<Tuple>(t))...);
}
template<std::size_t... I, std::size_t... N, class Tuple, class... F>
inline auto multi_function_impl0(std::index_sequence<I...>, std::index_sequence<N...>, Tuple&& t, F... f)
{
constexpr std::size_t ns[] = {N...};
constexpr auto offsets = scan(ns);
return multi_function_impl1(std::index_sequence<offsets[I]...>(), std::index_sequence<N...>(), std::forward<Tuple>(t), f...);
}
template<std::size_t... N, class Tuple, class... F>
auto multi_function(Tuple&& t, F... f)
{
return multi_function_impl0(std::make_index_sequence<sizeof...(N)>(), std::index_sequence<N...>(), std::forward<Tuple>(t), f...);
}
其中pack
和Array
分别类似于std::make_tuple
和std::array
,但要克服一些问题:
std::make_tuple
衰减它的参数,因此引用丢失
std::array
不能用 c++14 中的 constexpr 编写它的元素
这个解决方案应该具有线性时间复杂度。它使用 std::tie
而不是 std::make_tuple
,因此函数和参数都不会被不必要地复制。与此处的其他一些答案相比,我认为应该相当容易理解。
首先,我们需要一个实用程序来使用 std::tuple
个参数调用函数。
template <typename F, typename Args, std::size_t... Is>
auto invoke_impl(F const& f, Args const& args, std::index_sequence<Is...>)
{
return f(std::get<Is>(args)...);
}
template <typename F, typename Args>
auto invoke(F const& f, Args const& args)
{
return invoke_impl(f, args, std::make_index_sequence<std::tuple_size<Args>::value>());
}
其次,我们需要一个实用程序来 std::tie
元组元素的子范围。
template <std::size_t Offset, typename Tuple, std::size_t... Is>
auto sub_tie_impl(Tuple const& tuple, std::index_sequence<Is...>)
{
return std::tie(std::get<Offset + Is>(tuple)...);
}
template <std::size_t Offset, std::size_t Count, typename Tuple>
auto sub_tie(Tuple const& tuple)
{
return sub_tie_impl<Offset>(tuple, std::make_index_sequence<Count>());
}
现在我们可以创建我们的实用程序来使用一系列函数来使用 std::tuple
个参数。
首先我们std::tie
将函数放入一个元组中,然后我们将参数列表拆分为子参数列表的参数包,最后我们为每个子参数列表调用一个函数,打包结果进入一个元组,然后我们 return.
template <typename Fs, std::size_t... Is, typename... SubArgs>
auto consume_impl(Fs const& fs, std::index_sequence<Is...>, SubArgs const&... sub_args)
{
return std::make_tuple(invoke(std::get<Is>(fs), sub_args)...);
}
template <std::size_t, typename Args, typename Fs, typename... SubArgs>
auto consume_impl(Args const&, Fs const& fs, SubArgs const&... sub_args)
{
return consume_impl(fs, std::make_index_sequence<sizeof...(SubArgs)>(), sub_args...);
}
template <std::size_t Offset, std::size_t Count, std::size_t... Counts,
typename Args, typename Fs, typename... SubArgs>
auto consume_impl(Args const& args, Fs const& fs, SubArgs const&... sub_args)
{
return consume_impl<Offset + Count, Counts...>(args, fs, sub_args...,
sub_tie<Offset, Count>(args));
}
template <std::size_t... Counts, typename Args, typename... Fs>
auto consume(Args const& args, Fs const&... fs)
{
return consume_impl<0, Counts...>(args, std::tie(fs...));
}
这是我遵循 T.C. 的建议后的解决方案,添加到我以前的(尽管效率低下)解决方案中:
#include <iostream>
#include <tuple>
#include <utility>
struct NoReturnValue {
friend std::ostream& operator<< (std::ostream& os, const NoReturnValue&) {
return os << "[no value returned]";
}
};
template <std::size_t N, typename Tuple>
struct TupleHead {
static auto get (const Tuple& tuple) { // The subtuple from the first N components of tuple.
return std::tuple_cat (TupleHead<N-1, Tuple>::get(tuple), std::make_tuple(std::get<N-1>(tuple)));
}
};
template <typename Tuple>
struct TupleHead<0, Tuple> {
static auto get (const Tuple&) { return std::tuple<>{}; }
};
template <std::size_t N, typename Tuple>
struct TupleTail {
static auto get (const Tuple& tuple) { // The subtuple from the last N components of tuple.
return std::tuple_cat (std::make_tuple(std::get<std::tuple_size<Tuple>::value - N>(tuple)), TupleTail<N-1, Tuple>::get(tuple));
}
};
template <typename Tuple>
struct TupleTail<0, Tuple> {
static auto get (const Tuple&) { return std::tuple<>{}; }
};
template <typename Tuple, typename F, std::size_t... Is>
auto functionOnTupleHelper (const Tuple& tuple, F f, const std::index_sequence<Is...>&,
std::enable_if_t< !std::is_void<std::result_of_t<F(std::tuple_element_t<Is, Tuple>...)>>::value >* = nullptr) { // This overload is called only if f's return type is not void.
return std::make_tuple(f(std::get<Is>(tuple)...)); // Thanks to T.C.'s advice on returning a single tuple and then calling std::tuple_cat on all the single tuples.
}
template <typename Tuple, typename F, std::size_t... Is>
auto functionOnTupleHelper (const Tuple& tuple, F f, const std::index_sequence<Is...>&,
std::enable_if_t< std::is_void<std::result_of_t<F(std::tuple_element_t<Is, Tuple>...)>>::value >* = nullptr) { // This overload is called only if f's return type is void.
f(std::get<Is>(tuple)...);
return std::tuple<NoReturnValue>(); // Thanks to T.C.'s advice on returning std::tuple<NoReturnValue>() if the return type of 'f' is void.
}
template <typename Tuple, typename F>
auto functionOnTuple (const Tuple& tuple, F f) {
return functionOnTupleHelper (tuple, f, std::make_index_sequence<std::tuple_size<Tuple>::value>{});
}
template <typename Tuple, typename... Functions> struct MultiFunction;
template <typename Tuple, typename F, typename... Fs>
struct MultiFunction<Tuple, F, Fs...> {
template <std::size_t I, std::size_t... Is>
static inline auto execute (const Tuple& tuple, const std::index_sequence<I, Is...>&, F f, Fs... fs) {
const auto headTuple = TupleHead<I, Tuple>::get(tuple);
const auto tailTuple = TupleTail<std::tuple_size<Tuple>::value - I, Tuple>::get(tuple);
const auto r = functionOnTuple(headTuple, f); // Which overload of 'functionOnTupleHelper' is called dedends on whether f's return type is void or not.
return std::tuple_cat (r, MultiFunction<std::remove_const_t<decltype(tailTuple)>, Fs...>::execute (tailTuple, std::index_sequence<Is...>{}, fs...)); // T.C.'s idea of tuple_cat with all the single return tuples.
}
};
template <>
struct MultiFunction<std::tuple<>> {
static auto execute (const std::tuple<>&, std::index_sequence<>) { return std::tuple<>(); }
};
template <std::size_t... Is, typename Tuple, typename... Fs>
auto multiFunction (const Tuple& tuple, Fs... fs) {
return MultiFunction<Tuple, Fs...>::execute (tuple, std::index_sequence<Is...>{}, fs...);
}
// Testing
template <typename T> int foo (int, char) {std::cout << "foo<T>\n"; return 0;}
double bar (bool, double, long) {std::cout << "bar\n"; return 3.5;}
double bar (bool, int) {return 1.4;}
void voidFunction() {std::cout << "voidFunction\n";}
template <int...> bool baz (char, short, float) {std::cout << "baz<int...>\n"; return true;}
int main() {
const auto tuple = std::make_tuple(5, 'a', true, 3.5, 1000, 't', 2, 5.8);
const auto firstBar = [](bool b, double d, long l) {return bar(b, d, l);};
const auto t = multiFunction<2,3,0,3> (tuple, foo<bool>, firstBar, voidFunction, baz<2,5,1>); // Note that since 'bar' has an overload, we have to define 'firstBar' to indicate which 'bar' function we want to use.
std::cout << std::boolalpha << std::get<0>(t) << ' ' << std::get<1>(t) << ' ' << std::get<2>(t) << ' ' << std::get<3>(t) << '\n';
// 0 3.5 [no value returned] true
}
这是另一个借鉴 Barry 的 partial_apply
想法但完全避免使用他的 partial_sum
函数的解决方案。结果它更短了。我认为这在时间复杂度上是线性的。
#include <iostream>
#include <tuple>
#include <utility>
template <std::size_t Offset, typename F, typename Tuple, std::size_t... Is>
auto partial_apply_impl (F f, const Tuple& tuple, const std::index_sequence<Is...>&) {
return f(std::get<Offset + Is>(tuple)...);
}
template <typename Off, typename F, typename Tuple> // Off must be of type OffsetIndexSequence<A,B> only.
auto partial_apply (F f, const Tuple& tuple) {
return partial_apply_impl<Off::value>(f, tuple, typename Off::sequence{});
}
template <std::size_t Offset, std::size_t Size>
struct OffsetIndexSequence : std::integral_constant<std::size_t, Offset> {
using sequence = std::make_index_sequence<Size>;
};
template <typename Output, std::size_t... Is> struct OffsetIndexSequenceBuilder;
template <template <typename...> class P, typename... Out, std::size_t Offset, std::size_t First, std::size_t... Rest>
struct OffsetIndexSequenceBuilder<P<Out...>, Offset, First, Rest...> :
OffsetIndexSequenceBuilder<P<Out..., OffsetIndexSequence<Offset, First>>, Offset + First, Rest...> {};
template <template <typename...> class P, typename... Out, std::size_t Offset>
struct OffsetIndexSequenceBuilder<P<Out...>, Offset> {
using type = P<Out...>;
};
template <std::size_t... Is>
using offset_index_sequences = typename OffsetIndexSequenceBuilder<std::tuple<>, 0, Is...>::type;
template <typename> struct MultiFunction;
template <template <typename...> class P, typename... Offs>
struct MultiFunction<P<Offs...>> {
template <typename ArgsTuple, typename... Fs>
static auto execute (const ArgsTuple& argsTuple, Fs... fs) {
using ResultTuple = std::tuple<decltype(partial_apply<Offs>(fs, argsTuple))...>;
return ResultTuple{partial_apply<Offs>(fs, argsTuple)...};
}
};
template <std::size_t... Is, typename ArgsTuple, typename... Fs>
auto multiFunction (const ArgsTuple& argsTuple, Fs... fs) {
return MultiFunction<offset_index_sequences<Is...>>::execute(argsTuple, fs...);
}
// Testing
int foo (int, char) {std::cout << "foo\n"; return 0;}
double bar (bool, double, long) {std::cout << "bar\n"; return 3.5;}
bool baz (char, short, float) {std::cout << "baz\n"; return true;}
int main() {
const auto tuple = std::make_tuple(5, 'a', true, 3.5, 1000, 't', 2, 5.8);
const std::tuple<int, double, bool> t = multiFunction<2,3,3> (tuple, foo, bar, baz); // foo bar baz
std::cout << std::boolalpha << std::get<0>(t) << ' ' << std::get<1>(t) << ' ' << std::get<2>(t) << '\n'; // 0 3.5 true
}
考虑这个输出:
int foo (int, char) {std::cout << "foo\n"; return 0;}
double bar (bool, double, long ) {std::cout << "bar\n"; return 3.5;}
bool baz (char, short, float) {std::cout << "baz\n"; return true;}
int main() {
const auto tuple = std::make_tuple(5, 'a', true, 3.5, 1000, 't', 2, 5.8);
multiFunction<2,3,3> (tuple, foo, bar, baz); // foo bar baz
}
所以multiFunction<2,3,3>
取tuple
的前2个元素传递给foo
,tuple
的后3个元素传递给bar
,等等......我得到了这个工作(除非函数有重载,这是一个单独的问题)。但是每个调用的函数的 return 值都丢失了。我希望将这些 return 值存储在某处,例如
std::tuple<int, double, bool> result = multiFunction<2,3,3> (tuple, foo, bar, baz);
但我不知道如何实现。对于那些想要帮助完成这项工作的人,这里是我目前的(更新的)工作代码,它只将输出存储到一个字符串流中。取回所有值并不容易,特别是如果流中保存的对象很复杂 类.
#include <iostream>
#include <tuple>
#include <utility>
#include <sstream>
template <std::size_t N, typename Tuple>
struct TupleHead {
static auto get (const Tuple& tuple) { // The subtuple from the first N components of tuple.
return std::tuple_cat (TupleHead<N-1, Tuple>::get(tuple), std::make_tuple(std::get<N-1>(tuple)));
}
};
template <typename Tuple>
struct TupleHead<0, Tuple> {
static auto get (const Tuple&) { return std::tuple<>{}; }
};
template <std::size_t N, typename Tuple>
struct TupleTail {
static auto get (const Tuple& tuple) { // The subtuple from the last N components of tuple.
return std::tuple_cat (std::make_tuple(std::get<std::tuple_size<Tuple>::value - N>(tuple)), TupleTail<N-1, Tuple>::get(tuple));
}
};
template <typename Tuple>
struct TupleTail<0, Tuple> {
static auto get (const Tuple&) { return std::tuple<>{}; }
};
template <typename Tuple, typename F, std::size_t... Is>
auto functionOnTupleHelper (const Tuple& tuple, F f, const std::index_sequence<Is...>&) {
return f(std::get<Is>(tuple)...);
}
template <typename Tuple, typename F>
auto functionOnTuple (const Tuple& tuple, F f) {
return functionOnTupleHelper (tuple, f, std::make_index_sequence<std::tuple_size<Tuple>::value>{});
}
template <typename Tuple, typename... Functions> struct MultiFunction;
template <typename Tuple, typename F, typename... Fs>
struct MultiFunction<Tuple, F, Fs...> {
template <std::size_t I, std::size_t... Is>
static inline auto execute (const Tuple& tuple, std::ostringstream& oss, const std::index_sequence<I, Is...>&, F f, Fs... fs) {
const auto headTuple = TupleHead<I, Tuple>::get(tuple);
const auto tailTuple = TupleTail<std::tuple_size<Tuple>::value - I, Tuple>::get(tuple);
// functionOnTuple (headTuple, f); // Always works, though return type is lost.
oss << std::boolalpha << functionOnTuple (headTuple, f) << '\n'; // What about return types that are void???
return MultiFunction<std::remove_const_t<decltype(tailTuple)>, Fs...>::execute (tailTuple, oss, std::index_sequence<Is...>{}, fs...);
}
};
template <>
struct MultiFunction<std::tuple<>> {
static auto execute (const std::tuple<>&, std::ostringstream& oss, std::index_sequence<>) { // End of recursion.
std::cout << std::boolalpha << oss.str();
// Convert 'oss' into the desired tuple? But how?
return std::tuple<int, double, bool>(); // This line is just to make the test compile.
}
};
template <std::size_t... Is, typename Tuple, typename... Fs>
auto multiFunction (const Tuple& tuple, Fs... fs) {
std::ostringstream oss;
return MultiFunction<Tuple, Fs...>::execute (tuple, oss, std::index_sequence<Is...>{}, fs...);
}
// Testing
template <typename T> int foo (int, char) {std::cout << "foo<T>\n"; return 0;}
double bar (bool, double, long ) {std::cout << "bar\n"; return 3.5;}
template <int...> bool baz (char, short, float) {std::cout << "baz<int...>\n"; return true;}
int main() {
const auto tuple = std::make_tuple(5, 'a', true, 3.5, 1000, 't', 2, 5.8);
std::tuple<int, double, bool> result = multiFunction<2,3,3> (tuple, foo<bool>, bar, baz<2,5,1>); // foo<T> bar baz<int...>
}
这是一种贪婪地推导参数数量的方法:
#include <tuple>
namespace detail {
using namespace std;
template <size_t, size_t... Is, typename Arg>
constexpr auto call(index_sequence<Is...>, Arg&&) {return tuple<>{};}
template <size_t offset, size_t... Is, typename ArgT, typename... Fs>
constexpr auto call(index_sequence<Is...>, ArgT&&, Fs&&...);
template <size_t offset, size_t... Is,
typename ArgT, typename F, typename... Fs,
typename=decltype(declval<F>()(get<offset+Is>(declval<ArgT>())...))>
constexpr auto call(index_sequence<Is...>, ArgT&& argt, F&& f, Fs&&... fs) {
return tuple_cat(make_tuple(f(get<offset+I>(forward<ArgT>(argt))...)),
call<offset+sizeof...(Is)>(index_sequence<>{},
forward<ArgT>(argt),
forward<Fs>(fs)...));}
template <size_t offset, size_t... Is, typename ArgT, typename... Fs>
constexpr auto call(index_sequence<Is...>, ArgT&& argt, Fs&&... fs) {
return call<offset>(index_sequence<Is..., sizeof...(Is)>{},
forward<ArgT>(argt), forward<Fs>(fs)...);}
}
template <typename ArgT, typename... Fs>
constexpr auto multifunction(ArgT&& argt, Fs&&... fs) {
return detail::call<0>(std::index_sequence<>{},
std::forward<ArgT>(argt), std::forward<Fs>(fs)...);}
Demo。但是,上面的 return 值的数量具有二次时间复杂度,因为 tuple_cat
是递归调用的。相反,我们可以使用略微修改的 call
版本来获取每次调用的索引 - 然后直接获取实际的 tuple
:
#include <tuple>
namespace detail {
using namespace std;
template <size_t, size_t... Is, typename Arg>
constexpr auto indices(index_sequence<Is...>, Arg&&) {return tuple<>{};}
template <size_t offset, size_t... Is, typename ArgT, typename... Fs>
constexpr auto indices(index_sequence<Is...>, ArgT&&, Fs&&...);
template <size_t offset, size_t... Is, typename ArgT, typename F, class... Fs,
typename=decltype(declval<F>()(get<offset+Is>(declval<ArgT>())...))>
constexpr auto indices(index_sequence<Is...>, ArgT&& argt, F&& f, Fs&&... fs){
return tuple_cat(make_tuple(index_sequence<offset+Is...>{}),
indices<offset+sizeof...(Is)>(index_sequence<>{},
forward<ArgT>(argt),
forward<Fs>(fs)...));}
template <size_t offset, size_t... Is, typename ArgT, typename... Fs>
constexpr auto indices(index_sequence<Is...>, ArgT&& argt, Fs&&... fs) {
return indices<offset>(index_sequence<Is..., sizeof...(Is)>{},
forward<ArgT>(argt), forward<Fs>(fs)...);}
template <typename Arg, typename F, size_t... Is>
constexpr auto apply(Arg&& a, F&& f, index_sequence<Is...>) {
return f(get<Is>(a)...);}
template <typename ITuple, typename Args, size_t... Is, typename... Fs>
constexpr auto apply_all(Args&& args, index_sequence<Is...>, Fs&&... fs) {
return make_tuple(apply(forward<Args>(args), forward<Fs>(fs),
tuple_element_t<Is, ITuple>{})...);
}
}
template <typename ArgT, typename... Fs>
constexpr auto multifunction(ArgT&& argt, Fs&&... fs) {
return detail::apply_all<decltype(detail::indices<0>(std::index_sequence<>{},
std::forward<ArgT>(argt),
std::forward<Fs>(fs)...))>
(std::forward<ArgT>(argt), std::index_sequence_for<Fs...>{},
std::forward<Fs>(fs)...);}
从头开始构建并忽略完美转发,这样我就可以减少输入。我们需要几个帮手。首先,我们需要一个 apply 的部分版本,它从我们想要应用到函数的元组中获取哪些索引:
<class Tuple, class F, size_t... Is>
auto partial_apply(Tuple tuple, F f, std::index_sequence<Is...>) {
return f(get<Is>(tuple)...);
}
然后,我们需要为元组的每个子集调用该函数。假设我们已经将所有函数和索引包装在一个元组中:
template <class Tuple, class FsTuple, class IsTuple, size_t... Is>
auto multi_apply(Tuple tuple, FsTuple fs, IsTuple indexes, std::index_sequence<Is...>) {
return std::make_tuple(
partial_apply(tuple,
std::get<Is>(fs),
std::get<Is>(indexes)
)...
);
}
所以在这种情况下,我们希望最终调用 multi_apply(tuple, <foo,bar,baz>, <<0,1>,<2,3,4>,<5,6,7>>, <0, 1, 2>)
。
我们只需要构建 indexes
部分。我们从 <2,3,3>
开始。我们需要获得部分和 (<0,2,5>
) 并将其添加到索引序列 <<0,1>,<0,1,2>,<0,1,2>>
。所以我们可以写一个偏和函数:
template <size_t I>
using size_t_ = std::integral_constant<size_t, I>;
template <class R, size_t N>
R partial_sum_(std::index_sequence<>, R, size_t_<N> ) {
return R{};
}
template <size_t I, size_t... Is, size_t... Js, size_t S>
auto partial_sum_(std::index_sequence<I, Is...>,
std::index_sequence<Js...>, size_t_<S> )
{
return partial_sum_(std::index_sequence<Is...>{},
std::index_sequence<Js..., S>{}, size_t_<S+I>{} );
}
template <size_t... Is>
auto partial_sum_(std::index_sequence<Is...> is)
{
return partial_sum_(is, std::index_sequence<>{}, size_t_<0>{} );
};
我们可以使用它来生成我们所有的索引作为一个元组:
template <size_t... Is, size_t N>
auto increment(std::index_sequence<Is...>, size_t_<N> )
{
return std::index_sequence<Is+N...>{};
}
template <class... Seqs, size_t... Ns>
auto make_all_indexes(std::index_sequence<Ns...>, Seqs... seqs)
{
return std::make_tuple(increment(seqs, size_t_<Ns>{})...);
}
像这样:
template <size_t... Is, class Tuple, class... Fs>
auto multiFunction(Tuple tuple, Fs... fs)
{
static_assert(sizeof...(Is) == sizeof...(Fs));
return multi_apply(tuple,
std::make_tuple(fs...),
make_all_indexes(
partial_sum_(std::index_sequence<Is...>{}),
std::make_index_sequence<Is>{}...
),
std::make_index_sequence<sizeof...(Is)>{}
);
}
如果你想处理void
returns,那么就把partial_apply
return做成一个单一元素的元组(或者一个空元组),然后改变make_tuple()
在 multi_apply
到 tuple_cat()
中的用法。
还有一个例子:
template<std::size_t N>
constexpr Array<std::size_t, N> scan(std::size_t const (&a)[N])
{
Array<std::size_t, N> b{};
for (int i = 0; i != N - 1; ++i)
b[i + 1] = a[i] + b[i];
return b;
}
template<std::size_t O, std::size_t... N, class F, class Tuple>
inline decltype(auto) eval_from(std::index_sequence<N...>, F f, Tuple&& t)
{
return f(std::get<N + O>(std::forward<Tuple>(t))...);
}
template<std::size_t... O, std::size_t... N, class Tuple, class... F>
inline auto multi_function_impl1(std::index_sequence<O...>, std::index_sequence<N...>, Tuple&& t, F... f)
{
return pack(eval_from<O>(std::make_index_sequence<N>(), f, std::forward<Tuple>(t))...);
}
template<std::size_t... I, std::size_t... N, class Tuple, class... F>
inline auto multi_function_impl0(std::index_sequence<I...>, std::index_sequence<N...>, Tuple&& t, F... f)
{
constexpr std::size_t ns[] = {N...};
constexpr auto offsets = scan(ns);
return multi_function_impl1(std::index_sequence<offsets[I]...>(), std::index_sequence<N...>(), std::forward<Tuple>(t), f...);
}
template<std::size_t... N, class Tuple, class... F>
auto multi_function(Tuple&& t, F... f)
{
return multi_function_impl0(std::make_index_sequence<sizeof...(N)>(), std::index_sequence<N...>(), std::forward<Tuple>(t), f...);
}
其中pack
和Array
分别类似于std::make_tuple
和std::array
,但要克服一些问题:
std::make_tuple
衰减它的参数,因此引用丢失std::array
不能用 c++14 中的 constexpr 编写它的元素
这个解决方案应该具有线性时间复杂度。它使用 std::tie
而不是 std::make_tuple
,因此函数和参数都不会被不必要地复制。与此处的其他一些答案相比,我认为应该相当容易理解。
首先,我们需要一个实用程序来使用 std::tuple
个参数调用函数。
template <typename F, typename Args, std::size_t... Is>
auto invoke_impl(F const& f, Args const& args, std::index_sequence<Is...>)
{
return f(std::get<Is>(args)...);
}
template <typename F, typename Args>
auto invoke(F const& f, Args const& args)
{
return invoke_impl(f, args, std::make_index_sequence<std::tuple_size<Args>::value>());
}
其次,我们需要一个实用程序来 std::tie
元组元素的子范围。
template <std::size_t Offset, typename Tuple, std::size_t... Is>
auto sub_tie_impl(Tuple const& tuple, std::index_sequence<Is...>)
{
return std::tie(std::get<Offset + Is>(tuple)...);
}
template <std::size_t Offset, std::size_t Count, typename Tuple>
auto sub_tie(Tuple const& tuple)
{
return sub_tie_impl<Offset>(tuple, std::make_index_sequence<Count>());
}
现在我们可以创建我们的实用程序来使用一系列函数来使用 std::tuple
个参数。
首先我们std::tie
将函数放入一个元组中,然后我们将参数列表拆分为子参数列表的参数包,最后我们为每个子参数列表调用一个函数,打包结果进入一个元组,然后我们 return.
template <typename Fs, std::size_t... Is, typename... SubArgs>
auto consume_impl(Fs const& fs, std::index_sequence<Is...>, SubArgs const&... sub_args)
{
return std::make_tuple(invoke(std::get<Is>(fs), sub_args)...);
}
template <std::size_t, typename Args, typename Fs, typename... SubArgs>
auto consume_impl(Args const&, Fs const& fs, SubArgs const&... sub_args)
{
return consume_impl(fs, std::make_index_sequence<sizeof...(SubArgs)>(), sub_args...);
}
template <std::size_t Offset, std::size_t Count, std::size_t... Counts,
typename Args, typename Fs, typename... SubArgs>
auto consume_impl(Args const& args, Fs const& fs, SubArgs const&... sub_args)
{
return consume_impl<Offset + Count, Counts...>(args, fs, sub_args...,
sub_tie<Offset, Count>(args));
}
template <std::size_t... Counts, typename Args, typename... Fs>
auto consume(Args const& args, Fs const&... fs)
{
return consume_impl<0, Counts...>(args, std::tie(fs...));
}
这是我遵循 T.C. 的建议后的解决方案,添加到我以前的(尽管效率低下)解决方案中:
#include <iostream>
#include <tuple>
#include <utility>
struct NoReturnValue {
friend std::ostream& operator<< (std::ostream& os, const NoReturnValue&) {
return os << "[no value returned]";
}
};
template <std::size_t N, typename Tuple>
struct TupleHead {
static auto get (const Tuple& tuple) { // The subtuple from the first N components of tuple.
return std::tuple_cat (TupleHead<N-1, Tuple>::get(tuple), std::make_tuple(std::get<N-1>(tuple)));
}
};
template <typename Tuple>
struct TupleHead<0, Tuple> {
static auto get (const Tuple&) { return std::tuple<>{}; }
};
template <std::size_t N, typename Tuple>
struct TupleTail {
static auto get (const Tuple& tuple) { // The subtuple from the last N components of tuple.
return std::tuple_cat (std::make_tuple(std::get<std::tuple_size<Tuple>::value - N>(tuple)), TupleTail<N-1, Tuple>::get(tuple));
}
};
template <typename Tuple>
struct TupleTail<0, Tuple> {
static auto get (const Tuple&) { return std::tuple<>{}; }
};
template <typename Tuple, typename F, std::size_t... Is>
auto functionOnTupleHelper (const Tuple& tuple, F f, const std::index_sequence<Is...>&,
std::enable_if_t< !std::is_void<std::result_of_t<F(std::tuple_element_t<Is, Tuple>...)>>::value >* = nullptr) { // This overload is called only if f's return type is not void.
return std::make_tuple(f(std::get<Is>(tuple)...)); // Thanks to T.C.'s advice on returning a single tuple and then calling std::tuple_cat on all the single tuples.
}
template <typename Tuple, typename F, std::size_t... Is>
auto functionOnTupleHelper (const Tuple& tuple, F f, const std::index_sequence<Is...>&,
std::enable_if_t< std::is_void<std::result_of_t<F(std::tuple_element_t<Is, Tuple>...)>>::value >* = nullptr) { // This overload is called only if f's return type is void.
f(std::get<Is>(tuple)...);
return std::tuple<NoReturnValue>(); // Thanks to T.C.'s advice on returning std::tuple<NoReturnValue>() if the return type of 'f' is void.
}
template <typename Tuple, typename F>
auto functionOnTuple (const Tuple& tuple, F f) {
return functionOnTupleHelper (tuple, f, std::make_index_sequence<std::tuple_size<Tuple>::value>{});
}
template <typename Tuple, typename... Functions> struct MultiFunction;
template <typename Tuple, typename F, typename... Fs>
struct MultiFunction<Tuple, F, Fs...> {
template <std::size_t I, std::size_t... Is>
static inline auto execute (const Tuple& tuple, const std::index_sequence<I, Is...>&, F f, Fs... fs) {
const auto headTuple = TupleHead<I, Tuple>::get(tuple);
const auto tailTuple = TupleTail<std::tuple_size<Tuple>::value - I, Tuple>::get(tuple);
const auto r = functionOnTuple(headTuple, f); // Which overload of 'functionOnTupleHelper' is called dedends on whether f's return type is void or not.
return std::tuple_cat (r, MultiFunction<std::remove_const_t<decltype(tailTuple)>, Fs...>::execute (tailTuple, std::index_sequence<Is...>{}, fs...)); // T.C.'s idea of tuple_cat with all the single return tuples.
}
};
template <>
struct MultiFunction<std::tuple<>> {
static auto execute (const std::tuple<>&, std::index_sequence<>) { return std::tuple<>(); }
};
template <std::size_t... Is, typename Tuple, typename... Fs>
auto multiFunction (const Tuple& tuple, Fs... fs) {
return MultiFunction<Tuple, Fs...>::execute (tuple, std::index_sequence<Is...>{}, fs...);
}
// Testing
template <typename T> int foo (int, char) {std::cout << "foo<T>\n"; return 0;}
double bar (bool, double, long) {std::cout << "bar\n"; return 3.5;}
double bar (bool, int) {return 1.4;}
void voidFunction() {std::cout << "voidFunction\n";}
template <int...> bool baz (char, short, float) {std::cout << "baz<int...>\n"; return true;}
int main() {
const auto tuple = std::make_tuple(5, 'a', true, 3.5, 1000, 't', 2, 5.8);
const auto firstBar = [](bool b, double d, long l) {return bar(b, d, l);};
const auto t = multiFunction<2,3,0,3> (tuple, foo<bool>, firstBar, voidFunction, baz<2,5,1>); // Note that since 'bar' has an overload, we have to define 'firstBar' to indicate which 'bar' function we want to use.
std::cout << std::boolalpha << std::get<0>(t) << ' ' << std::get<1>(t) << ' ' << std::get<2>(t) << ' ' << std::get<3>(t) << '\n';
// 0 3.5 [no value returned] true
}
这是另一个借鉴 Barry 的 partial_apply
想法但完全避免使用他的 partial_sum
函数的解决方案。结果它更短了。我认为这在时间复杂度上是线性的。
#include <iostream>
#include <tuple>
#include <utility>
template <std::size_t Offset, typename F, typename Tuple, std::size_t... Is>
auto partial_apply_impl (F f, const Tuple& tuple, const std::index_sequence<Is...>&) {
return f(std::get<Offset + Is>(tuple)...);
}
template <typename Off, typename F, typename Tuple> // Off must be of type OffsetIndexSequence<A,B> only.
auto partial_apply (F f, const Tuple& tuple) {
return partial_apply_impl<Off::value>(f, tuple, typename Off::sequence{});
}
template <std::size_t Offset, std::size_t Size>
struct OffsetIndexSequence : std::integral_constant<std::size_t, Offset> {
using sequence = std::make_index_sequence<Size>;
};
template <typename Output, std::size_t... Is> struct OffsetIndexSequenceBuilder;
template <template <typename...> class P, typename... Out, std::size_t Offset, std::size_t First, std::size_t... Rest>
struct OffsetIndexSequenceBuilder<P<Out...>, Offset, First, Rest...> :
OffsetIndexSequenceBuilder<P<Out..., OffsetIndexSequence<Offset, First>>, Offset + First, Rest...> {};
template <template <typename...> class P, typename... Out, std::size_t Offset>
struct OffsetIndexSequenceBuilder<P<Out...>, Offset> {
using type = P<Out...>;
};
template <std::size_t... Is>
using offset_index_sequences = typename OffsetIndexSequenceBuilder<std::tuple<>, 0, Is...>::type;
template <typename> struct MultiFunction;
template <template <typename...> class P, typename... Offs>
struct MultiFunction<P<Offs...>> {
template <typename ArgsTuple, typename... Fs>
static auto execute (const ArgsTuple& argsTuple, Fs... fs) {
using ResultTuple = std::tuple<decltype(partial_apply<Offs>(fs, argsTuple))...>;
return ResultTuple{partial_apply<Offs>(fs, argsTuple)...};
}
};
template <std::size_t... Is, typename ArgsTuple, typename... Fs>
auto multiFunction (const ArgsTuple& argsTuple, Fs... fs) {
return MultiFunction<offset_index_sequences<Is...>>::execute(argsTuple, fs...);
}
// Testing
int foo (int, char) {std::cout << "foo\n"; return 0;}
double bar (bool, double, long) {std::cout << "bar\n"; return 3.5;}
bool baz (char, short, float) {std::cout << "baz\n"; return true;}
int main() {
const auto tuple = std::make_tuple(5, 'a', true, 3.5, 1000, 't', 2, 5.8);
const std::tuple<int, double, bool> t = multiFunction<2,3,3> (tuple, foo, bar, baz); // foo bar baz
std::cout << std::boolalpha << std::get<0>(t) << ' ' << std::get<1>(t) << ' ' << std::get<2>(t) << '\n'; // 0 3.5 true
}