web-dev-qa-db-fra.com

Nombre variable d'arguments C ++ 11, même type spécifique

La question est simple, comment pourrais-je implémenter une fonction prenant un nombre variable d'arguments (comme le modèle variadic), cependant où tous les arguments ont le même type, disons int.

Je pensais à quelque chose de pareil;

void func(int... Arguments)

Sinon, une assertion statique récursive sur les types ne fonctionne-t-elle pas?

44
Skeen

Une solution possible consiste à faire du type de paramètre un conteneur qui peut être initialisé par une liste d'initialisation d'accolade, telle que std::initializer_list<int> ou std::vector<int> . Par exemple :

#include <iostream>
#include <initializer_list>

void func(std::initializer_list<int> a_args)
{
    for (auto i: a_args) std::cout << i << '\n';
}

int main()
{
    func({4, 7});
    func({4, 7, 12, 14});
}
47
hmjd

Voici une version qui supprime la fonction de l'ensemble de surcharge, au lieu de donner un static_assert. Cela vous permet de fournir d'autres surcharges de la fonction qui pourraient être utilisées lorsque les types ne sont pas tous identiques, plutôt qu'un fatal static_assert qui ne peut pas être évité.

#include <type_traits>

template<typename... T>
  struct all_same : std::false_type { };

template<>
  struct all_same<> : std::true_type { };

template<typename T>
  struct all_same<T> : std::true_type { };

template<typename T, typename... Ts>
  struct all_same<T, T, Ts...> : all_same<T, Ts...> { };

template<typename... T>
typename std::enable_if<all_same<T...>::value, void>::type
func(T...)
{ }

Si vous souhaitez prendre en charge un transfert parfait, vous souhaiterez probablement désintégrer les types avant de les vérifier, afin que la fonction accepte un mélange d'arguments lvalue et rvalue tant qu'ils ont le même type:

template<typename... T>
typename std::enable_if<all_same<typename std::decay<T>::type...>::value, void>::type
func(T&&...)
{ }

Alternativement, si vous avez un trait général pour tester la conjonction logique, vous pouvez le faire en utilisant std::is_same au lieu d'écrire votre propre all_same:

template<typename T, typename... Ts>
typename std::enable_if<and_<is_same<T, Ts>...>::value, void>::type
func(T&&, Ts&&...)
{ }

Parce que cela nécessite au moins un argument, vous auriez également besoin d'une autre surcharge pour prendre en charge le cas zéro argument:

void func() { }

Le and_ helper peut être défini comme suit:

template<typename...>
  struct and_;

template<>
  struct and_<>
  : public std::true_type
  { };

template<typename B1>
  struct and_<B1>
  : public B1
  { };

template<typename B1, typename B2>
  struct and_<B1, B2>
  : public std::conditional<B1::value, B2, B1>::type
  { };

template<typename B1, typename B2, typename B3, typename... Bn>
  struct and_<B1, B2, B3, Bn...>
  : public std::conditional<B1::value, and_<B2, B3, Bn...>, B1>::type
  { };
17
Jonathan Wakely

Je pense que vous pouvez le faire en spécifiant un type concret lorsque vous mâchez vos arguments du pack d'arguments. Quelque chose comme:

class MyClass{};
class MyOtherClass{};

void func()
{
    // do something
}

template< typename... Arguments >
void func( MyClass arg, Arguments ... args )
{
    // do something with arg
    func( args... );
    // do something more with arg
}


void main()
{
    MyClass a, b, c;
    MyOtherClass d;
    int i;
    float f;

    func( a, b, c );    // compiles fine
    func( i, f, d );    // cannot convert
}

Dans le cas générique void func( MyClass arg, Arguments ... args ) deviendrait void func( arg, Arguments ... args ) avec un type de modèle T.

12
user2746401

@Skeen Et ça?

template <typename T>
void func_1(std::initializer_list<T>&& a) {
    // do something
} 

template <typename... T>
void func(T&&... a) {
    func_1({std::forward<T>(a)...});
} 

int main() {
    func(1, 2, 3);
    // func(1, 2, 3, 4.0); // OK doesn't compile
}
4
dusketha

Si vous ne souhaitez pas utiliser d'accolade initializer_list/vector et souhaitez garder les arguments séparés sous forme de pack d'arguments, puis la solution ci-dessous le vérifie au moment de la compilation en utilisant récursif static_asserts:

#include<type_traits>

template<typename T1, typename T2, typename... Error>
struct is_same : std::false_type {};

template<typename T, typename... Checking>
struct is_same<T, T, Checking...> : is_same<T, Checking...> {}; 

template<typename T>
struct is_same<T,T> : std::true_type {};

template<typename... LeftMost>
void func (LeftMost&&... args)
{
  static_assert(is_same<typename std::decay<LeftMost>::type...>::value, 
                "All types are not same as 'LeftMost'");
  // ...
}

int main ()
{
  int var = 2;
  func(1,var,3,4,5);  // ok
  func(1,2,3,4.0,5); // error due to `static_assert` failure
}

En fait, cette solution vérifierait tous les arguments par rapport au premier argument. Supposons que ce soit double alors tout sera vérifié par rapport à double.

3
iammilind

Parce que je ne pense pas avoir vu cette solution, vous pouvez écrire une fonction spécifique pour chaque type (dans votre cas, juste int) puis une fonction de transfert prenant des types d'arguments variadiques.

Écrivez chaque cas spécifique:

puis pour chaque cas spécifique:

// only int in your case
void func(int i){
    std::cout << "int i = " << i << std::endl;
}

Ensuite, votre fonction de transfert comme ceci:

template<typename Arg0, typename Arg1 typename ... Args>
void func(Arg0 &&arg0, Arg1 &&arg1, Args &&... args){
    func(std::forward<Arg0>(arg0));
    func(std::forward<Arg1>(arg1), std::forward<Args>(args)...);
}

C'est bien car il est extensible pour quand vous voulez accepter peut-être un autre type aussi.

Utilisé comme ceci:

int main(){
    func(1, 2, 3, 4); // works fine
    func(1.0f, 2.0f, 3.0f, 4.0f); // compile error, no func(float)
}
1
CoffeeandCode