Question Passer de nombreuses fonctions et stocker tous leurs résultats dans un tuple


Considérez cette sortie:

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
}

Alors multiFunction<2,3,3> prend les 2 premiers éléments de tuple et les passe à foo, les 3 prochains éléments de tuple et les passe à bar, etc ... J'ai ce travail (sauf lorsque les fonctions ont des surcharges, ce qui est un problème distinct). Mais les valeurs de retour de chaque fonction appelée sont perdues. Je veux que ces valeurs de retour stockées quelque part, quelque chose comme

std::tuple<int, double, bool> result = multiFunction<2,3,3> (tuple, foo, bar, baz);

Mais je ne sais pas comment le mettre en œuvre. Pour ceux qui veulent aider à réaliser cela, voici mon code de travail (mis à jour) à ce jour, qui stocke les sorties uniquement dans une chaîne de caractères. Pas facile de récupérer toutes les valeurs, surtout si les objets enregistrés dans le flux sont des classes complexes.

#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...>
}

10
2018-03-18 15:04


origine


Réponses:


Voici une approche où le nombre d'arguments est déduit avidement:

#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)...);}

Démo. Cependant, ce qui précède a une complexité de temps quadratique dans le nombre de valeurs de retour, car tuple_cat est appelé récursivement. Au lieu de cela, nous pouvons utiliser une version légèrement modifiée de call pour obtenir les indices pour chaque appel - le réel tuple est alors obtenu directement:

#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)...);}

Démo 2.


7
2018-03-18 16:12



Construire à partir de zéro et ignorer la transmission parfaite pour que je tape moins. Nous avons besoin de quelques aides. Tout d'abord, nous avons besoin d'une version partielle de apply qui prend quels index du tuple que nous voulons appliquer à la fonction:

<class Tuple, class F, size_t... Is>
auto partial_apply(Tuple tuple, F f, std::index_sequence<Is...>) {
    return f(get<Is>(tuple)...);
}

Ensuite, nous devons appeler cette fonction pour chaque sous-ensemble du tuple. Disons que nous avons déjà toutes nos fonctions et index dans un 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)
            )...
        );
}

Donc, dans ce cas, nous voudrions finir par appeler multi_apply(tuple, <foo,bar,baz>, <<0,1>,<2,3,4>,<5,6,7>>, <0, 1, 2>).

Tout ce dont nous avons besoin de savoir, c'est de construire le indexes partie. Nous commençons avec <2,3,3>. Nous devons obtenir les sommes partielles (<0,2,5>) et ajouter cela aux séquences d'index <<0,1>,<0,1,2>,<0,1,2>>. Nous pouvons donc écrire une fonction de somme partielle:

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>{} );
};

Que nous pouvons utiliser pour générer tous nos index en tant que tuple:

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>{})...);
}

Ainsi:

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)>{}
        );

}

Si vous voulez gérer void retourne, puis faites juste partial_apply renvoyer un tuple d'un seul élément (ou d'un tuple vide) et changer le make_tuple() utilisation dans multi_apply à tuple_cat().


4
2018-03-18 16:16



Voici encore un autre aspect:

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 et Array sont similaires à std::make_tuple et std::array respectivement, mais pour surmonter certains problèmes:

  • std::make_tuple le désintègre args, donc les références sont perdues
  • std::array ne peut pas avoir ses élems écrits en constexpr dans c ++ 14

DEMO


2
2018-03-18 17:37



Voici ma solution après avoir suivi les conseils de T.C., en ajoutant à ma solution précédente (quoique inefficace):

#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
}

2
2018-03-19 01:36



Cette solution devrait avoir une complexité de temps linéaire. Il utilise std::tie au lieu de std::make_tuple, donc ni les fonctions ni les arguments ne sont copiés inutilement. Je pense que cela devrait être assez facile à suivre par rapport à d’autres réponses ici.

Tout d'abord, nous avons besoin d'un utilitaire pour invoquer une fonction en utilisant un std::tuple des arguments.

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>());
}

Deuxièmement, nous avons besoin d'un utilitaire pour std::tie une sous-gamme d'éléments de tuple.

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>());
}

Maintenant, nous pouvons créer notre utilitaire pour consommer un std::tuple des arguments en utilisant une séquence de fonctions.

Premièrement, nous std::tie les fonctions dans un tuple, puis nous divisons la liste des arguments en un paquet de paramètres de sous-arguments, et finalement nous invoquons une fonction pour chaque liste de sous-arguments, en plaçant les résultats dans un tuple que nous retournons ensuite.

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...));
}

2
2018-03-18 18:49



Voici une autre solution empruntant Barry's partial_apply idée mais en évitant l'utilisation de son partial_sum Fonctionne tout à fait. C'est plus court en conséquence. Je pense que cela est linéaire dans la complexité du temps.

#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
}

0
2018-03-20 17:12