web-dev-qa-db-fra.com

Pourquoi C++ ne prend-il pas en charge les points de suspension fortement typés?

Quelqu'un peut-il m'expliquer s'il vous plaît pourquoi le C++, du moins à ma connaissance, n'implémente pas une fonction fortement typée Ellipsis, ce qui a pour effet

void foo(double ...) {
 // Do Something
}

Ce qui signifie que, en termes clairs: "L'utilisateur peut transmettre un nombre variable de termes à la fonction foo, cependant, tous les termes doivent être doubles"

29
Nicholas Hamilton

Historiquement, la syntaxe Ellipsis ... vient de C.

Cette bête compliquée a été utilisée pour alimenter des fonctions similaires à printf- et doit être utilisée avec va_list, va_start etc ...

Comme vous l'avez noté, ce n'est pas typesafe; mais alors C est loin d’être typé, avec ses conversions implicites de et en void* pour tous types de pointeurs, sa troncature implicite d’intégrales/valeurs à virgule flottante, etc.

Parce que C++ devait être aussi proche que possible d’un sur-ensemble de C, il a hérité des Ellipsis de C.


Depuis sa création, les pratiques en C++ ont évolué et un puissant effort en faveur d’un typage renforcé a été mis en place.

En C++ 11, cela a abouti à:

  • listes d'initialisation, syntaxe abrégée pour un nombre variable de valeurs d'un type donné: foo({1, 2, 3, 4, 5})
  • les modèles variadiques, qui sont une bête à part entière et permettent d'écrire un printf type-safe, par exemple

Les modèles variadiques réutilisent réellement Ellipsis ... dans leur syntaxe, pour indiquer packs de types ou de valeurs et en tant qu’opérateur de décompression:

void print(std::ostream&) {}

template <typename T, typename... Args>
void print(std::ostream& out, T const& t, Args const&... args) {
    print(out << t, args...); // recursive, unless there are no args left
                              // (in that case, it calls the first overload
                              // instead of recursing.)
}

Notez les 3 utilisations différentes de ...:

  • typename... pour déclarer un type variadique
  • Args const&... pour déclarer un paquet d'arguments
  • args... pour décompresser le pack dans une expression
15
Matthieu M.

Il y a 

 void foo(std::initializer_list<double> values);
 // foo( {1.5, 3.14, 2.7} );

qui est très proche de cela.

Vous pouvez également utiliser des modèles variadiques mais cela devient plus discursif. En ce qui concerne la raison réelle, je dirais que les efforts pour intégrer cette nouvelle syntaxe n'en valent probablement pas la peine: comment accéder aux éléments individuels? Comment savez-vous quand arrêter? Qu'est-ce qui le rend meilleur que, par exemple, std::initializer_list?

C++ a encore quelque chose de plus proche de cela: des packs de paramètres non typés.

template < non-type ... values>

comme dans

template <int ... Ints>
void foo()
{
     for (int i : {Ints...} )
         // do something with i
}

mais le type du paramètre template non-type (uhm) comporte certaines restrictions: il ne peut pas être double, par exemple.

23
edmz

C'est déjà possible avec les templates variadic et SFINAE:

template <bool...> struct bool_pack;
template <bool... v>
using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;

template <class... Doubles, class = std::enable_if_t<
    all_true<std::is_convertible<Doubles, double>{}...>{}
>>
void foo(Doubles... args) {}

Merci à Columbo pour l'astuce de Nice all_true. Vous pourrez également utiliser une expression de pliage en C++ 17.

Etant donné que les normes futures et à venir se concentrent sur la syntaxe de testeur (boucles de forçage, modèles de fonction implicites, etc.), il est fort possible que la syntaxe proposée finisse dans la norme un jour;)

11
Quentin

Pourquoi précisément une telle chose n'a pas été proposée (ou a été proposée et rejetée), je l'ignore. Une telle chose serait certainement utile, mais ajouterait une complexité supplémentaire au langage. Comme Quentin en fait la démonstration, il existe déjà un moyen de réaliser cela avec les modèles en C++ 11. 

Lorsque Concepts sera ajouté à la norme, nous aurons un autre moyen plus concis:

template <Convertible<double>... Args>
void foo(Args... doubles);

ou

template <typename... Args>
    requires Convertible<Args, double>()...
void foo(Args... doubles);

ou, comme indiqué par @dyp :

void foo(Convertible<double>... doubles);    

Personnellement, entre la solution actuelle et celles que nous aurons avec Concepts, j'estime que c'est une solution adéquate au problème. Surtout que le dernier est fondamentalement ce que vous aviez initialement demandé de toute façon. 

2
Barry
template<typename T, typename... Arguments>
struct are_same;

template <typename T, typename A1, typename... Args>
struct are_same<T, A1, Args...>{    static const bool value = std::is_same<T, A1>::value && are_same<T, Args...>::value;};

template <typename T>
struct are_same<T>{static const bool value = true;};

template<typename T, typename... Arguments>
using requires_same = std::enable_if_t<are_same<T, Arguments...>::value>;

template <typename... Arguments, typename = requires_same<double, Arguments...>>
void foo(Arguments ... parameters)
{
}
1
Simon Kraemer

Basé sur la réponse de Matthew :

void foo () {}

template <typename... Rest>
void foo (double arg, Rest... rest)
{
    /* do something with arg */
    foo(rest...);
}

Si le code utilisant foo est compilé, vous savez que tous les arguments sont convertibles en double.

1
jxh

Le moyen de réaliser ce que vous suggérez est d'utiliser des modèles variadiques

template<typename... Arguments>
void foo(Arguments... parameters);

cependant, vous pouvez maintenant passer n'importe quel type dans le pack de paramètres. Ce que vous proposez n’a jamais été mis en oeuvre, cela pourrait peut-être être un excellent ajout au langage, ou cela pourrait être trop difficile à mettre en oeuvre dans l’état actuel des choses. Vous pouvez toujours essayer d’écrire une proposition et de la soumettre à isocpp.org

1
dau_sama

Parce que tu peux utiliser

void foo(std::vector<T> values);
0
SingerOfTheFall