77 votes

Comment faire des calculs génériques sur des packs de hétérogène argument d’une fonction de modèle variadique ?

PRÉMISSE:

Après avoir joué avec variadic templates un peu, j'ai réalisé que la réalisation de tout ce qui va légèrement au-delà de la banalité de la méta-programmation des tâches devient vite assez lourd. En particulier, j'ai trouvé moi-même qui souhaitent un moyen pour effectuer générique opérations de plus de un argument pack comme itérer, split, boucle en std::for_each-comme la mode, et ainsi de suite.

Après avoir regardé cette conférence par Andrei Alexandrescu à partir de C++ et au-Delà de 2012 sur l'opportunité d' static if en C++ (un concept emprunté à la D Langage de Programmation), j'ai eu le sentiment que quelque sorte d' static for viendrait pratique aussi bien - et je me sens plus de ces static constructions pourraient apporter un bénéfice.

J'ai donc commencé à me demander si il existe un moyen de parvenir à quelque chose comme cela pour l'argument packs d'un variadic template de fonction (pseudo-code):

template<typename... Ts>
void my_function(Ts&&... args)
{
    static for (int i = 0; i < sizeof...(args); i++) // PSEUDO-CODE!
    {
        foo(nth_value_of<i>(args));
    }
}

Qui serait traduit au moment de la compilation en quelque chose comme ceci:

template<typename... Ts>
void my_function(Ts&&... args)
{
    foo(nth_value_of<0>(args));
    foo(nth_value_of<1>(args));
    // ...
    foo(nth_value_of<sizeof...(args) - 1>(args));
}

En principe, static_for permettrait encore plus élaborée de traitement:

template<typename... Ts>
void foo(Ts&&... args)
{
    constexpr s = sizeof...(args);

    static for (int i = 0; i < s / 2; i++)
    {
        // Do something
        foo(nth_value_of<i>(args));
    }

    static for (int i = s / 2; i < s; i++)
    {
        // Do something different
        bar(nth_value_of<i>(args));
    }
}

Ou pour une plus grande expressivité d'un langage comme celui-ci:

template<typename... Ts>
void foo(Ts&&... args)
{
    static for_each (auto&& x : args)
    {
        foo(x);
    }
}

TRAVAUX CONNEXES:

J'ai fait quelques recherche sur le Web et découvert que quelque chose existe bel et bien:

  • Ce lien explique comment convertir un paramètre pack en un coup de pouce.MPL vecteur, mais qui ne fait que la moitié du chemin (si ce n'est moins) en direction de l'objectif;
  • cette question DONC, semble appeler à un semblable et légèrement liée méta-programmation (fractionnement d'un argument pack en deux moitiés) - en fait, il ya plusieurs questions DONC, qui semble être lié à ce problème, mais aucune réponse, j'ai lu de la résoudre de façon satisfaisante à mon humble avis;
  • Coup de pouce.Fusion définit les algorithmes de la conversion d'un argument pack dans un tuple, mais je préfère:
    1. de ne pas créer d' inutiles temporaires de tenir des arguments qui peuvent (et devraient) être parfaitement transmis à certains algorithmes génériques;
    2. avoir un petit, autonome de la bibliothèque pour faire cela, tandis que Boost.La Fusion est susceptible d'inclure de façon plus de choses que ce qui est nécessaire pour remédier à ce problème.

QUESTION:

Est-il un moyen relativement simple, éventuellement par le biais de certains modèle de méta-programmation, de réaliser ce que je suis à la recherche d', sans encourir dans les limites des approches existantes?

65voto

Andy Prowl Points 62121

Puisque je n'étais pas heureux avec ce que j'ai trouvé, j'ai essayé de trouver une solution moi-même et a fini par écrire une petite bibliothèque qui permet la formulation générique des opérations sur l'argument packs. Ma solution a les caractéristiques suivantes:

  • Permet de parcourir l'ensemble ou certains des éléments d'un argument pack, éventuellement définis par le calcul de leurs indices sur l'emballage;
  • Permet le transfert calculée parties d'un argument pack de variadic foncteurs;
  • Exige seulement dont un relativement court fichier d'en-tête;
  • Fait un usage intensif de transfert parfait pour permettre lourd inline et évite copies/se déplace pour permettre le minimum de perte de performances;
  • La mise en œuvre interne de l'itération algorithmes repose sur le Vide de la Classe de Base d'Optimisation pour réduire la consommation de mémoire.
  • Il est facile (relativement, étant donné que c'est le modèle de la méta-programmation) d'étendre et de s'adapter.

Je vais d'abord montrer ce qui peut être fait avec la bibliothèque, puis afficher sa mise en œuvre.

LES CAS D'UTILISATION

Voici un exemple de la façon dont l' for_each_in_arg_pack() fonction peut être utilisée pour parcourir tous les arguments d'un pack et passer chaque argument en entrée de certains fournie par le client foncteur (bien sûr, le foncteur doit avoir un générique d'appel de l'opérateur si l'argument pack contient les valeurs de la hétérogène types):

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

L' print foncteur ci-dessus peut également être utilisé dans plus de calculs complexes. En particulier, voici comment itérer sur un sous-ensemble (dans ce cas, une sous-gamme) des arguments dans un pack:

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

Parfois, on peut juste vouloir transférer une partie d'un argument pack de certains autres variadic foncteur au lieu d'une itération à travers ses éléments et passer chacun d'eux individuellement à un non-variadic foncteur. C'est ce que l' forward_subpack() algorithme permet de faire:

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

Pour des tâches plus spécifiques, il est bien sûr possible de récupérer les arguments spécifiques dans un pack à l' indexation . C'est ce que l' nth_value_of() fonction vous permet d'effectuer, en collaboration avec ses assistants first_value_of() et 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;
}

Si l'argument pack est homogène sur l'autre main (c'est à dire tous les arguments du même type), une formulation telle que celui-ci serait peut-être préférable. L' is_homogeneous_pack<> méta-fonction permet de déterminer si tous les types de paramètre pack sont homogènes, et est principalement destinée à être utilisée en static_assert() déclarations:

// 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;
}

Enfin, depuis les lambdas sont tout sucre syntaxique pour les foncteurs, ils peuvent être utilisés en combinaison avec les algorithmes ci-dessus; toutefois, jusqu'à ce générique lambdas seront pris en charge par le C++, ce n'est possible que pour homogène argument packs. L'exemple suivant montre également l'utilisation de l' homogeneous-type<> méta-fonction, qui renvoie le type de tous les arguments dans un milieu homogène pack:

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

C'est fondamentalement ce que la bibliothèque permet d'en faire, mais je crois qu'il pourrait même être étendu à l'accomplissement de tâches plus complexes.

La mise en ŒUVRE

Maintenant vient la mise en place, ce qui est un peu plus difficile par elle-même, donc je vais utiliser des commentaires pour expliquer le code et éviter de faire ce post trop long (peut-être c'est déjà fait):

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

CONCLUSION

Bien sûr, même si j'ai ma propre réponse à cette question (et en fait à cause de ce fait), je suis curieux de savoir si des solutions de rechange ou de meilleures solutions existent qui j'ai manqué - en dehors de ceux mentionnés dans le "Travaille" de la question.

10voto

Marc Glisse Points 2377

Je me permets de poster ce code, issu de la discussion :

J’ai vérifié le code généré avec et ne contient que 3 appels à `` , il n’y a aucune trace de l’aides d’expansion.

5voto

Matthieu M. Points 101624

À l’aide d’une solution d’énumérer (ala Python).

Utilisation :

Code :

Le constructeur de la gamme (pilferred de votre solution) :

0voto

dspeyer Points 972

Le... la notation il a quelques options intéressantes, comme :

Malheureusement, je ne sais pas de toute façon pour faire respecter l’ordre dans lequel les fonctions d’impression sont appelées (vers l’arrière, sur mon compilateur). Notez qu’impression doit retourner quelque chose.

Cette astuce peut être utile si vous vous en fichez sur commande.

0voto

pje Points 806

Après la lecture de quelques posts et de bricolage pour un moment, je suis venu avec la suite (un peu comme le dessus, mais la mise en œuvre est un peu différent). J'ai écrit ceci à l'aide de Visual Studio 2013 compilateur.

Utilisation à l'aide d'une expression lambda -

static_for_each()(
    [](std::string const& str)
    {
        std::cout << str << std::endl;
    }, "Hello, ", "Lambda!");

L'inconvénient lors de l'utilisation d'un lambda est les paramètres doivent être du même type déclaré dans le lambda de la liste des paramètres. Cela signifie qu'il ne fonctionne qu'avec un seul type. Si vous souhaitez utiliser basé sur un modèle de fonction, vous pouvez utiliser l'exemple suivant.

L'utilisation de l'aide struct wrapper foncteur -

struct print_wrapper
{
    template <typename T>
    void operator()(T&& str)
    {
        std::cout << str << " ";
    }
};

// 
// A little test object we can use.
struct test_object
{
    test_object() : str("I'm a test object!") {}
    std::string str;
};

std::ostream& operator<<(std::ostream& os, test_object t)
{
    os << t.str;
    return os;
}

//
// prints: "Hello, Functor! 1 2 I'm a test object!"
static_for_each()(print_wrapper(), "Hello,", "Functor!", 1, 2.0f, test_object());

Cela vous permet de passer dans tous les types que vous souhaitez et de les manipuler à l'aide du foncteur. J'ai trouvé cette jolie propre et fonctionne très bien pour ce que je voulais. Vous pouvez aussi l'utiliser avec un paramètre de la fonction pack comme celui-ci

template <typename T, typename... Args>
void call(T f, Args... args)
{
    static_for_each()(f, args...);
}

call(print_wrapper(), "Hello", "Call", "Wrapper!");

Ici est la mise en œuvre -

// 
// Statically iterate over a parameter pack 
// and call a functor passing each argument.
struct static_for_each
{
private:
    // 
    // Get the parameter pack argument at index i.
    template <size_t i, typename... Args>
    static auto get_arg(Args&&... as) 
    -> decltype(std::get<i>(std::forward_as_tuple(std::forward<Args>(as)...)))
    {
        return std::get<i>(std::forward_as_tuple(std::forward<Args>(as)...));
    }

    //
    // Recursive template for iterating over 
    // parameter pack and calling the functor.
    template <size_t Start, size_t End>
    struct internal_static_for
    {
        template <typename Functor, typename... Ts>
        void operator()(Functor f, Ts&&... args)
        {
            f(get_arg<Start>(args...));
            internal_static_for<Start + 1, End>()(f, args...);
        }
    };

    //
    // Specialize the template to end the recursion.
    template <size_t End>
    struct internal_static_for<End, End>
    {
        template <typename Functor, typename... Ts>
        void operator()(Functor f, Ts&&... args){}
    };

public:
    // 
    // Publically exposed operator()(). 
    // Handles template recursion over parameter pack.
    // Takes the functor to be executed and a parameter 
    // pack of arguments to pass to the functor, one at a time.
    template<typename Functor, typename... Ts>
    void operator()(Functor f, Ts&&... args)
    {
        // 
        // Statically iterate over parameter
        // pack from the first argument to the
        // last, calling functor f with each 
        // argument in the parameter pack.
        internal_static_for<0u, sizeof...(Ts)>()(f, args...);
    }
};

L'espérance de gens trouvent cela utile :-)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X