2
votes

Existe-t-il un moyen de définir un nombre variadique d'arguments du même type?

Je n'arrive pas à comprendre comment implémenter une fonction avec un nombre variable d'arguments du même type.

J'écris pour un microcontrôleur avec peu de pile et de mémoire, donc je ne peux pas utiliser la récursivité ou le STL (les parties avec des exceptions).

Est-il possible de créer une telle fonction?

S s;
const int mode=3, speed=1;

fun<&s,1,2,7,4>(mode,speed);

qui se développe en quelque chose comme ceci:

for(int arg:args){ 
s->r1+=7*arg;
}

exemple d'appel:

struct S{
int r1;
int r2;
};
template<S* s, int... args> fun(int arg1, int arg2);


2 commentaires

int ... args est correct. Avez-vous un code réel qui l'utilise qui ne fonctionne pas?


@logicstuff Cela existe depuis C ++ 11. Vous pouvez avoir un pack de types ou de valeurs.


3 Réponses :


0
votes

Malheureusement, il n'existe actuellement aucun moyen de spécifier un pack de paramètres de fonction où chaque paramètre est du même type. La prochaine meilleure chose que vous puissiez obtenir (pour autant que je sache) serait une fonction qui prend un nombre variable d'arguments de n'importe quel type tant que le type de tous est int : XXX

exemple en direct ici

L'astuce ici est de s'appuyer sur SFINAE pour supprimer efficacement toutes les versions de la fonction où tous les arguments ne finissent pas par être de type int

Pour votre exemple concret, vous pourriez faire, par exemple:

#include <type_traits>

struct S
{
    int r1;
    int r2;
};

template <S& s, typename... Args>
auto f(Args... args) -> std::enable_if_t<(std::is_same_v<Args, int> && ...)>
{
    ((s.r1 += 7 * args), ...);
}

S s;
const int mode=3, speed=1;

void test()
{
    f<s>(mode, speed);
}

démo en direct ici


0 commentaires

1
votes

Je n'arrive pas à comprendre comment implémenter une fonction avec un nombre variable d'arguments du même type.

Argument de modèle du même type ou arguments de fonction ordinaires du même type?

Le premier cas est simple (si le type est un type admis pour les types de valeur de modèle), exactement comme vous l'avez écrit p>

#include <utility>
#include <type_traits>

struct S
{
    int r1;
    int r2;
};

S s;
const int mode=3, speed=1;

template <typename T, std::size_t>
using getType = T;

template <std::size_t N, typename = std::make_index_sequence<N>>
struct bar;

template <std::size_t N, std::size_t ... Is>
struct bar<N, std::index_sequence<Is...>>
 {
   static constexpr auto f (getType<int, Is> ... args)
    { ((s.r1 += 7 * args), ...); }
 };

template <S &, std::size_t N = 64u, typename = std::make_index_sequence<N>>
struct foo;

template <S & s, std::size_t N, std::size_t ... Is>
struct foo<s, N, std::index_sequence<Is...>> : public bar<Is>...
 { using bar<Is>::f...; };

int main ()
 {
   foo<s>::f(mode, speed);
 }

et vous pouvez les utiliser en utilisant le pliage de modèle, si vous pouvez utiliser C ++ 17,

fun(1, 2, 3l); // compilation error (3l is a long int, not an int)

ou dans un peu plus compliqué avant (C ++ 11 / C ++ 14)

template <typename ... Args>
auto fun (Args ... args)

Malheureusement, vous pouvez appeler ce type de fonction avec des entiers connus au moment de la compilation donc, par exemple, pas avec variables

int a = 7;
fun<&s,1,2,a,4>(mode,speed); // compilation error

Dans ce cas, vous avez besoin d'une liste variadique d'arguments de fonction ordinaires du même type; malheureusement c'est un peu plus compliqué.

Vous pouvez créer une liste variadique typique de paramètres de modèle

template <S* s, int... args>
auto fun (int arg1, int arg2)
 { 
   using unused = int[];

   (void)unused { 0, s->r1 += 7 * args ... };
 }

en imposant, via SFINAE, que tout Les arguments ... sont déduits ou expliqués comme int (voir la réponse de Michael Kenzel).

Malheureusement, cela nécessite que chaque argument soit exactement si le type int donc appeler func avec (par exemple) un long int donne une erreur de compilation

template <S* s, int... args>
auto fun (int arg1, int arg2)
 { ((s->r1 += 7 * args), ...); }

Vous pouvez évidemment relâcher la condition SFINAE imposant (en exemple) que tous les types Args ... sont convertibles ( std :: is_convertible ) en int mais n'a pas exactement le développement d'une fonction recevant un nombre variadique d'arguments du même type.

Si vous pouvez accepter une limite supérieure au nombre d'arguments ( 64 , dans l'exemple suivant) et que la fonction est méthode (peut-être statique) d'une classe, vous pouvez créer une classe foo contenant une méthode f () qui reçoit zéro int , un f () qui reçoit un int , un f () qui reçoit deux int s, etc., jusqu'à ce qu'un f () qui reçoive 63 ints.

Ce qui suit est une compilation complète de C ++ 17 exemple

template<S* s, int... args>
fun (int arg1, int arg2);

En C ++ 14, c'est un peu plus compliqué car il n'y a pas de variadique utilisant donc vous devez écrire le foo class de manière récursive.

En C ++ 11, vous devez également développer un substitut pour std :: make_index_sequence / std :: index_sequence .


0 commentaires

1
votes

Faire en sorte que tous les paramètres d'un pack variadique soient du même type peut être requis avec les concepts C ++ 20.

Malheureusement, à partir de C ++ 20, la bibliothèque standard n'a pas de concept pour all_same (il n'y a que std :: same_as pour deux types), mais il peut être facilement défini:

template <typename AlwaysVoid, typename... Ts>
struct has_common_type_impl : std::false_type {};

template <typename... Ts>
struct has_common_type_impl<std::void_t<std::common_type_t<Ts...>>, Ts...>
    : std::true_type {};

template <typename... Ts>
concept has_common_type = 
    sizeof...(Ts) < 2 ||
    has_common_type_impl<void, Ts...>::value;

template<typename... Ts> requires has_common_type<Ts...>
void foo(Ts&&... ts) {}

Code: https://godbolt.org/z/dH9t-N


Notez que dans de nombreux cas, il n'est pas nécessaire d'exiger le même type, vous pouvez à la place exiger que tous les arguments aient un type commun . Pour exiger un type commun, basé sur tester si un type commun existe em > vous pouvez avoir le concept suivant:

template<class... Ts>
concept all_same = 
        sizeof...(Ts) < 2 ||
        std::conjunction_v<
            std::is_same<std::tuple_element_t<0, std::tuple<Ts...>>, Ts>...
        >;

template<typename... Ts> requires all_same<Ts...>
void foo(Ts&&... ts) {}

Code: https : //godbolt.org/z/5M6dLp


5 commentaires

le truc du tuple est simplement: template struct head {using type = T; };


une version _t de head: template utilisant head_t = typename head :: type;


donnant: template concept all_same = (sizeof ... (Ts) <2) || (std :: is_same , Ts> :: value && ...);


template concept all_same = (sizeof ... (Ts) <2) || (std :: same_as , Ts> && ...);


@QuentinUK est soit same_as et is_same meilleur que l'autre et comment?