2015-08-28 2 views
25

Quelqu'un peut-il s'il vous plaît me expliquer pourquoi C++, au moins à ma connaissance, ne met pas en œuvre une fonction de points de suspension fortement typé, quelque chose à l'effet de:Pourquoi C++ ne supporte pas les ellipses fortement typées?

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

Ce qui signifie que, en parler clair: « L'utilisateur peut passer un nombre variable de termes à la fonction foo, cependant, toutes les conditions doivent être doubles'

+3

Je suppose que les fonctions variadiques ont été ajoutées à C dans le seul but de supporter la famille de fonctions printf, qui doit être de type dangereux. Le concept d'E/S de la chaîne de format elle-même vient probablement des prédécesseurs de C comme BCPL (voir https://en.wikipedia.org/wiki/BCPL). En C++ moderne, il n'est pas nécessaire d'introduire des fonctions variées de type sécurité, car nous avons de toute façon des constructions de langage supérieures, surtout depuis C++ 11. Malheureusement, je n'ai aucune référence pour mes suppositions. Il serait intéressant de poser cette question à Bjarne Stroustrup lui-même. –

+1

Vous pouvez faire 'void foo (double *)' et l'appeler par 'foo ((double []) {1,2,3,4,5})'. Besoin d'extension GNU C++. – user3528438

+0

Cette fonctionnalité n'est-elle pas trop anecdotique pour être incorporée dans un langage déjà sur-affiné? Alors vous devriez aussi réclamer 'void foo (double ..., int ..., double ...)' et similaire. –

Répondre

10

Historiquement, la syntaxe des points de suspension ... provient de C.

Cette bête complexe a été utilisé pour alimenter printf -comme fonctions et doit être utilisé avec va_list, va_start etc ...

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

Parce que C++ devait être aussi proche que possible comme une surcouche de C, il a hérité de points de suspension C.


depuis sa création, C++ pratiques ont évolué, et il y a eu une forte poussée vers frappe plus fort.

en C++ 11, ce a abouti à:

  • listes initialiseur, une syntaxe sténographie pour un nombre variable de valeurs d'un type donné: foo({1, 2, 3, 4, 5})
  • modèles variadique, qui sont une bête de leurs propres et permettent d'écrire un typées printf par exemple

modèles VARIADIC réutiliser réellement les points de suspension ... dans leur syntaxe, pour désigner paquets des types ou des valeurs et un opérateur Déballez:

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... de déclarer un type variadique
  • Args const&... de déclarer un paquet d'arguments
  • args... pour déballer le pack dans une expression
+0

L'appel à 'print' n'a pas beaucoup de sens. Où vont les 'args'? – Quentin

+4

@Quentin: Les fonctions variadiques fonctionnent souvent (aujourd'hui) avec récursion, donc vous appelez 'print' avec 4 arguments, qui appelle' print' avec 3 arguments, qui appelle 'print' avec 2 arguments, qui appelle' print' avec 1 argument => c'est le cas de base (fonction non-variadique) et la récursion s'arrête. –

+0

Je suis aveugle, je n'ai pas vu qu'il y avait deux surcharge de 'print'. Fait beaucoup plus de sens maintenant: p – Quentin

0

Parce que vous pouvez utiliser

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

... qui est de type sécurisé mais ne permet qu'un seul type ... et n'est pas très "naturel" d'appeler non plus. – DevSolar

+0

Je doute sérieusement que ce soit la raison. De plus, ce n'est pas du tout le même genre de chose. – juanchopanza

+0

@juan, s'il vous plaît élaborer. L'OP voulait un analogue d'une fonction avec une quantité variable d'arguments, tous les arguments étant d'un seul type (cela est clairement indiqué). Maintenant, qu'avons-nous ici, par rapport à cela? Une quantité variable d'arguments non nommés? Vérifier. Etre capable de compter les arguments réellement passés, et obtenir leurs valeurs? Vérifier. Bien sûr, ce n'est pas le _reason_, cependant, je pense qu'une fonction qui prend un vecteur de T répond entièrement à l'exigence OP de 'L'utilisateur peut passer un nombre variable de termes à la fonction foo, cependant, tous les termes doivent être de type T' – SingerOfTheFall

1

la façon d'atteindre (un peu) ce que vous proposez est de utiliser des modèles variésCependant, vous pouvez maintenant passer n'importe quel type dans le pack de paramètres. Ce que vous proposez n'a jamais été implémenté, peut-être pourrait-il être un excellent ajout à la langue, ou il pourrait être trop difficile à mettre en œuvre en l'état actuel des choses. Vous pouvez toujours essayer d'écrire une proposition et la soumettre à isocpp.org

+0

Ce n'est pas trop difficile à mettre en place. C'est en fait plus facile que des modèles variés. Cependant, l'article devrait (A) inclure une justification pourquoi cela est nécessaire, et donc pourquoi 'std :: initializer_list ' ne le fera pas, et probablement (B) expliquer comment 'sizeof ...' fonctionne pour 'double .. . » Points bonus pour (C) le libellé proposé pour la norme. – MSalters

19

Il est

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

qui est très proche de celui.

Vous pouvez également utiliser des modèles variadiques mais cela devient plus discursif. Quant à la raison réelle, je dirais que l'effort pour introduire cette nouvelle syntaxe ne vaut 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 quelque chose de plus proche encore de cela: les paquets 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 de modèle non de type (Uhm) a certaines restrictions: il ne peut pas être double, par exemple.

+4

et la valeur du (des) paramètre (s) de modèle non-type a des restrictions - il doit s'agir d'expressions de constante de compilation. –

10

Il est déjà possible avec des modèles variadique 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 all_true. Vous pourrez également utiliser une expression de pli en C++ 17.

Comme plus tard et à venir des normes se concentrent sur la syntaxe terser (laconique pour boucles, des modèles de fonction implicites ...) il est très possible que votre syntaxe proposée se termine dans la norme un jour,)

+0

Je ne peux pas le faire fonctionner: 'erreur C2783: 'void foo (Doubles ...)': impossible de déduire l'argument de modèle pour ''' –

+0

@SimonKraemer IIRC SFINAE est toutes sortes de cassé sur MSVC, mais malheureusement, je n'ai pas d'expérience dans la recherche de solutions de contournement ... – Quentin

+0

Même dans VS2015? Ok .... En attendant je construis moi-même une "solution de contournement": http://stackoverflow.com/a/32273936/4181011 –

2

Pour pourquoi spécifiquement, une telle chose n'a pas été proposée (ou a été proposée et rejetée), je ne sais pas. Une telle chose serait certainement utile, mais ajouterait plus de complexité à la langue. Comme le montre Quentin, il existe déjà une méthode C++ 11 pour réaliser une telle chose avec des modèles.

Lorsque Concepts est ajouté à la norme, nous aurons une autre, de façon plus concise:

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

ou

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

ou, comme @dyp points out:

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

Personnellement , entre la solution actuelle et celles que nous aurons avec Concepts, je pense que c'est un La solution au problème Surtout que le dernier est essentiellement ce que vous aviez initialement demandé de toute façon.

+1

Ou simplement 'void foo (Convertible ... doubles);' (que j'ai dû rechercher, mais est garanti de fonctionner par N4377, la raison étant probablement que toute fonction avec un espace réservé dans la clause parameter-decl-clause est * équivalent * à/défini comme un modèle de fonction) – dyp

+0

@dyp Personnellement, je ne suis pas un fan de cet usage car maintenant nous cachons que c'est un modèle de fonction. Bien que j'attende avec impatience tout le représentant que je vais avoir en répondant à des questions à ce sujet :) – Barry

+0

Oh, nous pouvons le faire * tout à fait clair * qu'il s'agit d'un modèle en utilisant le * pretty * 'AllConvertibleToDouble {... T} void foo (T ... t); '- Je ne peux pas obtenir gcc pour accepter' AllConvertible {... T} void foo (T ... t); 'malheureusement :(- hmm maintenant je veux définir un concept nommé 'Template' – dyp

1
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

Basé sur Matthew's answer:

void foo() {} 

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

Si le code à l'aide foo compiles, vous savez tous les arguments sont convertibles en double.