OVH Cloud OVH Cloud

Liste variable d'arguments

20 réponses
Avatar
Etienne Rousee
Bonjour,

Y a-t-il quelque chose de non standard dans ceci:

J'ai une classe Point2D contenant deux doubles
x et y et un constructeur, puis une classe Poly2D
contenant un deque<Point2D>. Le problème se
pose avec le constructeur de Poly2D qui admet
une liste variable d'arguments.
Voici son code:

// Début code -------------------

Poly2D::Poly2D(Point2D premier, ... )
{
va_list vl;
Point2D suivant;

if (premier == FIN) return;

va_start(vl,premier);
_pol.push_back(premier);
while (suivant != FIN)
{
suivant = va_arg(vl,Point2D);
if (suivant != FIN) _pol.push_back(suivant);
}
va_end(vl);
}

// Fin code -------------------

FIN est un Point2D fixe servant de marqueur de fin.

Puis je teste en mettant 3 Point2D en dur et en
écrivant Poly2D Pol(A,B,C,FIN);

Sous VC++6.0 tout fonctionne, 0 errors et 0 warnings,
et l'exécution donne ce que je veux.
Sous CodeBlocks, 0 errors et 1 warning que je ne comprends
pas, et la ligne de test ci dessus fait sortir du programme,
mais sans la fenêtre d'erreur windows traditionnelle.

Le warning est:
"cannot receive objects of non-POD type"
puis:
"cannot pass objects of non-POD type" 3 fois.

Que signifie POD ?
Y a-t-il une autre façon d'obtenir une liste variable d'arguments ?

--

Etienne

10 réponses

1 2
Avatar
Franck Branjonneau
"James Kanze" écrivait:

Etienne Rousee wrote:

[Constructeur à nombre variable d'arguments]

celle qui est à la mode :

On fait du constructeur un template, et on lui passe des
itérateurs :

celle [la ? solution] qui marche réelement :

On crée une classe « collecteur », qu'on passe.


Et dans le meilleur des mondes possibles :

#include <algorithm>
#include <iostream>
#include <iterator>
#include <ostream>
#include <vector>
#include <boost/assign/list_of.hpp>

struct Point {

double x_, y_;

Point(
double const x,
double const y):
x_(x),
y_(y) {
}

};

std::ostream &
operator<<(
std::ostream & os,
Point const & point) {

return os << '(' << point.x_ << ", " << point.y_ << ')';
}

struct Polygone {

std::vector< Point > data_;

template< typename _Iterator >
Polygone(
_Iterator const first,
_Iterator const last):
data_(first, last) {
}

};

Point A(0,0), B(1,0), C(1,1);

int
main() {

Polygone p(boost::assign::list_of(A)(B)(C)(Point(0,1)));

std::copy(p.data_.begin(), p.data_.end(),
std::ostream_iterator< Point >(std::cout));
std::cout << "n";
}

(Boost Assignment Library
<http://www.boost.org/libs/assign/doc/index.html#list_of_ref>)

Si on aime l'obfuscation, on peut même ajouter des
surcharges de l'opérateur virgule :

Ce qui permet l'écriture :

Poly2D poly( (Point2D( 1.2, 3.4 ),
unAutrePoint,
etEncoreUn) ) ;

(On remarque le deuxième jeu de parenthèse. C'est pourquoi
je le considère un peu l'obfuscation ; quelqu'un qui lit le
code risque de ne pas les voir de premier abord, et de ne
pas comprendre ce qui se passe. Mais au moins, si tu les
oublies, le compilateur râle.)


Moi j'aime bien ;-) Dommage que l'expression (A, B, ..., C) ne soit
pas le std::tuple<> qui va bien.

Enfin, si tu as une borne supérieur au nombre d'éléments, il y a
toujours le surcharge. Un peu pénible à entrée (surtout si la
borne supérieur est élevée. (Je me vois mal taper une centaine
de constructeurs, tous rigueureusement identique sauf en ce qui
concerne le nombre de paramètres. Mais un petit script d'AWK...)


Remplace ton AWK par un compilateur C++ ;-) :

Poly2D poly(tr1::make_tuple(Point2D(1.2, 3.4), unAutrePoint,
etEncoreUn));

avec un constructeur template et le define qui va bien pour le nombre
maximum d'éléments.

--
Boujou t'chu tè.
Franck Branjonneau

Avatar
James Kanze
Etienne Rousee wrote:
"James Kanze" a écrit ...
Comme a dit Sylvain, c'est un comportement indéfini. (En fait,
avec un compilateur moderne, on pourrait toujours le faire
marcher, mais historiquement, assurer la destruction sans savoir
combien d'éléments il y avait posait pas mal de problèmes.)


Il suffit que Poly2D ait un destructeur correct, non ?


Non. Selon la norme, « If the argument has a non-POD class
type, the behavior is undefined ». Pour une fois, c'est on ne
peut plus clair.

La motivation de cette contrainte est, je crois, le fait que
certains compilateurs primitifs appelaient en fait les
destructeurs à la fin de la fonction, dans la fonction, et
qu'évidemment, donnée un ..., on ne savait pas quels
destructeurs à appeler. Il y avait aussi, certainement, au moins
chez certains, le ressentiment que cette facilité n'avait pas
réelement sa place en C++, qu'on ne le supportait que pour des
raisons de compatibilité C, et que donc, il n'y avait pas besoin
de le supporter dans les contextes qui n'existaient pas en C.

La solution de Sylvain, avec des pointeurs (et un pointeur nul
comme marque de fin) marche, mais a deux inconveniants :
il ne permet pas de passer des temporaires (des expressions du
genre « Point2D( 1.2, 3.4 ) »),


Ben, ça me gêne aussi.

et il est trop facile d'oublier le pointeur nul de fin


C'est de la responsabilité du programmeur.


Tout est la responsabilité du programmeur. Mais il arrive quand
même que les programmeurs ne sont pas parfaits, et qu'ils font
des erreurs. Dans de tels cas, plus l'erreur est détectée tôt,
moins ça coûte.

(pointeur qui, au moins formellement, doit avoir
le type Point2D const* -- un 0 ou le symbole NULL
tout court ne suffit pas).


Un cast dans le constructeur ne suffit pas ?
Par exemple:
if (premier == (Point2D *) NULL) ...........


Pas dans le constructeur. Lors de l'appel :

Poly2D poly( &point1, &point2, &point3, (Point2D*)( NULL ) ) ;

Dans la pratique, ce qui risque de se passer, c'est que ça
marche en mode 32 bits, mais que tu as des erreurs aléatoires
(core dumps, etc.) lors de l'exécution en mode 64 bits.

Alors, il y a deux solutions classiques :

celle qui est à la mode :
...........
Il faut, par exemple, que tu as des paramètres
quelque part d'où tu peux tirer des itérateurs,
ce qui est loin d'être évident.


Je n'en ai pas justement.


Je m'en doutais. La proposition était un peu ironique, comme des
expressions du genre « est à la mode » devait laisser
entendre.

celle qui marche réelement :

On crée une classe « collecteur », qu'on passe. Le
constructeur devient à peu près :

Poly2D::Poly2D( Collector< Point2D > const& collector )
: _pol( collector.begin(), collector.end() )
{
}


Il y a une raison particulière pour que le collecteur soit template ?


Simplement parce qu'il soit généralement utilise pour ce genre
de chose. C'est un template chez moi, parce que je m'en suis
réelement servi pour plusieurs types différents.

Si tu suis ce chemin, évidemment, tu ne dois en faire un
template que :
-- une fois que tu as une version sans template qui marche, et
-- que tu as réelement besoin de supporter plusieurs types.
Et pas avant.

Dans mon posting, le but du template était surtout d'indiquer
que c'était une solution assez générique, qui ne dépendait en
rien des types précis de ton application. C'est tout.

Le collecteur, au font, ce n'est rien d'autre qu'une classe
qui contient un std::vector, et qui fournit aussi un moyen
d'enchaîner des push_back ; std::vector lui-même ferait
l'affaire si push_back renvoiait une référence à la
collection.


Est ce que Poly2D elle-même pourrait faire l'affaire ?


Pourquoi pas ? S'il a une sémantique de valeur (qu'il supporte la
copie et l'affectation, et n'est pas polymorphique), je dirais
que c'est même la solution la plus indiquer. Au moins que tu
tiens à ce que le nombre des vertices ne peut plus changer après
la construction. (Selon l'utilisation, il y a souvent un intérêt
à ce que les types de valeur soit « immutable » ; qu'on ne
peut en changer la valeur qu'avec l'affectation. Mais ce n'est
pas une règle absolue.)

À ce moment-là, il faudrait écrire :

Poly2D poly( Poly2D().with( point1 )
.with( point2 )
.with( point3 ) ) ;

ou :

Poly2D poly ;
poly.with( point1 ).with( point2 ).with( point3 ) ;

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
James Kanze
Mathias Gaunard wrote:
"James Kanze" a écrit ...

Si on aime l'obfuscation, on peut même ajouter des
surcharges de l'opérateur virgule :


J'aime bien :)

Ce qui permet l'écriture :

Poly2D poly( (Point2D( 1.2, 3.4 ),
unAutrePoint,
etEncoreUn) ) ;


Ce qui est exactement ce que je veux pouvoir écrire.
Tant pis pour les doubles parenthèses, il suffit de le savoir
et de le commenter.


Tu peux aussi utiliser d'autres opérateurs qui ne nécessiteront alors
pas de parenthèses.

Poly2D poly( Point2D(1.2, 3.4) % unAutrePoint % etEncoreUn );


Tu ne penses pas que ça pousse l'obfuscation un peu loin ?

Avec l'exception peut-être de l'opérateur << -- il a déjà deux
significations bien distinctes:-), et certains considèrent déjà
une de ses significations comme une sorte de collecteur.
Personnellement, j'y ressens surtout la sémantique de
formattage, qui n'y est pas présente ; je ne l'utiliserais donc
pas. Mais où tirer la ligne exacte ? Une personne que je
respecte beaucoup (celui qui m'a probablement appris le plus de
C++) argue que << signifie un formattage texte, et le rejette
même pour des flux de sortie binaire (formatté, genre XDR ou
BER), tandis que je m'y en sers. (<< a aussi l'avantage qu'il
est visiblement lourd -- il ne passe pas inaperçu --, et que
sa sémantique primitive sert assez peu, ce qui fait qu'on
s'attend un peu plus à une sémantique spéciale en le voyant..)

Une autre possibilité, c'est l'opérateur +=. Si Étienne se sert
du Poly2D directement comme collecteur, ça me semble même très
sensé : on ajoute un point au polygone. (Méfiance quant à
l'associativité, quand même. Une expression du genre :
Poly2D() += point1 += point2 += point3 ;
s'interprête :
Poly2D() += (point1 += (point2 += point3)) ;
Ce qui n'est pas forcément ce qu'on veut.)

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
James Kanze
On Feb 22, 8:56 pm, Franck Branjonneau wrote:
"James Kanze" écrivait:

Etienne Rousee wrote:

[Constructeur à nombre variable d'arguments]

celle qui est à la mode :
On fait du constructeur un template, et on lui passe des
itérateurs :
celle [la ? solution] qui marche réelement :

On crée une classe « collecteur », qu'on passe.


Et dans le meilleur des mondes possibles :


[...]

Polygone p(boost::assign::list_of(A)(B)(C)(Point(0,1)));


Je n'arrive pas à décider si c'est un cas de remplacer une
solution simple qu'on comprend bien par une complexe et
incompréhensible, ou si c'est un cas de remplacer une solution
qu'il faut implémenter soi-même avec du code déjà fait (et
testé):-).

Note qu'il y a des propositions d'ajouter plus de support pour
ce genre de chose dans la prochaine version de la norme. Je n'y
ai pas suivi les détails, mais je crois que c'est quelque chose
du genre permettre l'utilisation d'une "{...}" comme pour
l'initialisation d'un tableau de type C, que le compilateur
convertira en itérateurs.

[...]
Si on aime l'obfuscation, on peut même ajouter des
surcharges de l'opérateur virgule :

Ce qui permet l'écriture :

Poly2D poly( (Point2D( 1.2, 3.4 ),
unAutrePoint,
etEncoreUn) ) ;

(On remarque le deuxième jeu de parenthèse. C'est pourquoi
je le considère un peu l'obfuscation ; quelqu'un qui lit le
code risque de ne pas les voir de premier abord, et de ne
pas comprendre ce qui se passe. Mais au moins, si tu les
oublies, le compilateur râle.)


Moi j'aime bien ;-) Dommage que l'expression (A, B, ..., C) ne soit
pas le std::tuple<> qui va bien.


Le type de l'expression, c'est bien ce que renvoit l'opérateur
virgule. On en fait ce qu'on veut.

Enfin, si tu as une borne supérieur au nombre d'éléments, il y a
toujours le surcharge. Un peu pénible à entrée (surtout si la
borne supérieur est élevée. (Je me vois mal taper une centaine
de constructeurs, tous rigueureusement identique sauf en ce qui
concerne le nombre de paramètres. Mais un petit script d'AWK...)


Remplace ton AWK par un compilateur C++ ;-) :


Remplacer quelque chose de facilement lisible par quelque chose
que je ne comprendrais pas moi-même dans une semaine ? La
métaprogrammation avec templates est peut-être d'une puissance
extrème, mais il ne brille pas par sa lisabilité. Quand la
génération dépend des données connues seulement du compilateur
(des types, etc.), on n'a pas le choix, et on y passe. Mais
sinon, je préfère rester dans la lisible.

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Fabien LE LEZ
On 23 Feb 2007 01:13:11 -0800, "James Kanze" :

Une autre possibilité, c'est l'opérateur +=.


Barf. Dans le cas d'un objet mathématique (point, ensemble de points,
vecteur, etc.), mieux vaut AMHA laisser aux opérateurs mathématiques
(+, etc.) leur signification originelle.
D'autant qu'à un polygone, on peut très bien vouloir rajouter un
vecteur (qui, comme un point, est un agrégat de deux coordonnées),
pour effectuer une translation.

Avatar
Franck Branjonneau
"James Kanze" écrivait:

On Feb 22, 8:56 pm, Franck Branjonneau wrote:
"James Kanze" écrivait:

Si on aime l'obfuscation, on peut même ajouter des
surcharges de l'opérateur virgule :

Ce qui permet l'écriture :

Poly2D poly( (Point2D( 1.2, 3.4 ),
unAutrePoint,
etEncoreUn) ) ;


Moi j'aime bien ;-) Dommage que l'expression (A, B, ..., C) ne soit
pas le std::tuple<> qui va bien.


Le type de l'expression, c'est bien ce que renvoit l'opérateur
virgule. On en fait ce qu'on veut.


Oui, je voulais dire en "std" (un rel_ops qui fonctionne).

Enfin, si tu as une borne supérieur au nombre d'éléments, il y a
toujours le surcharge. Un peu pénible à entrée (surtout si la
borne supérieur est élevée. (Je me vois mal taper une centaine
de constructeurs, tous rigueureusement identique sauf en ce qui
concerne le nombre de paramètres. Mais un petit script d'AWK...)


Remplace ton AWK par un compilateur C++ ;-) :


Remplacer quelque chose de facilement lisible par quelque chose
que je ne comprendrais pas moi-même dans une semaine ?


C'est un argument imparable si ce n'est que moi j'écrirais un script
elisp. Qu'est-ce que tu préfères : te mettre au lisp ou au templates ?
;-) (Non, je ne me mettrais pas à AWK ;-)

La métaprogrammation avec templates est peut-être d'une puissance
extrème, mais il ne brille pas par sa lisabilité. Quand la
génération dépend des données connues seulement du compilateur (des
types, etc.), on n'a pas le choix, et on y passe. Mais sinon, je
préfère rester dans la lisible.


(*le* lisible) Tu n'as pas tort. Je n'avais pas abordé le problème
sous cet angle.

--
Franck Branjonneau



Avatar
Mathias Gaunard

Remplacer quelque chose de facilement lisible par quelque chose
que je ne comprendrais pas moi-même dans une semaine ? La
métaprogrammation avec templates est peut-être d'une puissance
extrème, mais il ne brille pas par sa lisabilité. Quand la
génération dépend des données connues seulement du compilateur
(des types, etc.), on n'a pas le choix, et on y passe. Mais
sinon, je préfère rester dans la lisible.


Je n'ai pas de problème particulier avec la lisibilité du code à base de
templates.
Au contraire, j'y trouve une certaine beauté.

Avatar
James Kanze
On Feb 23, 6:17 pm, Fabien LE LEZ wrote:
On 23 Feb 2007 01:13:11 -0800, "James Kanze" :

Une autre possibilité, c'est l'opérateur +=.


Barf. Dans le cas d'un objet mathématique (point, ensemble de points,
vecteur, etc.), mieux vaut AMHA laisser aux opérateurs mathématiques
(+, etc.) leur signification originelle.


Je suis tout à fait d'accord. Mais puisqu'on avait déjà proposé %.

En gros, je n'utilise le surcharge des opérateurs que dans
des cas précis :

-- des types « numériques »,

-- des tableaux (indicés par des valeurs numériques),

-- des pointeurs intelligents, et

-- des opérateurs d'insertion et d'extraction formattées d'un
flux.

Enfin, il y a quand même un quatrième cas dans la pratique :
quand mes utilisateurs insistent:-). (Ma première implémentation
pré-standard de String ne supportait pas []. J'ai dû
l'ajouter ; j'ai dû même en ajouter une version qui permettait
la modification de la chaîne.)

Je suis donc assez conservateur. Mais je conçois facilement que
des mathématiciens étendent cette notion à d'autres types
mathématiques où ils ont l'habitude des opérateurs ; je n'ai
pas trop de problème avec ceux qui s'étendent aussi l'indexation
pour accepter des indices des types arbitraire (encore que).
Mais j'arrive bien vite à ce que je considère des limites : un
itérateur a des fonctions next(), isDone() et current(), par
exemple, et si les noms peuvent varier, l'utilisation d'un
surcharge d'opérateur est un abus, qui nuit fortement à la
lisibilité du code.

(Je fais évidemment exception de l'opérateur d'affectation,
parce qu'il est là toujours. Dans certains rares cas,
l'opérateur d'appel de fonction -- operator()() -- se justifie
aussi ; en général, je préfère une bonne fonction bien
nommée, mais dans les templates, l'operator()() a l'avantage
qu'on peut se servir aussi d'un pointeur à une fonction.)

D'autant qu'à un polygone, on peut très bien vouloir rajouter
un vecteur (qui, comme un point, est un agrégat de deux
coordonnées), pour effectuer une translation.


En effet. S'il use le polygone même comme collecteur, je verais
bien une fonction « append() » ; avec un collecteur séparé (et des
polygones immutables sauf l'affectation), je préfère le nom
« with() ».

--
James Kanze (Gabi Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
James Kanze
On Feb 23, 11:06 pm, Franck Branjonneau wrote:
"James Kanze" écrivait:


[...]
Enfin, si tu as une borne supérieur au nombre d'éléments, il y a
toujours le surcharge. Un peu pénible à entrée (surtout si la
borne supérieur est élevée. (Je me vois mal taper une centaine
de constructeurs, tous rigueureusement identique sauf en ce qui
concerne le nombre de paramètres. Mais un petit script d'AWK...)


Remplace ton AWK par un compilateur C++ ;-) :


Remplacer quelque chose de facilement lisible par quelque chose
que je ne comprendrais pas moi-même dans une semaine ?


C'est un argument imparable si ce n'est que moi j'écrirais un script
elisp.


Tu mets de l'elisp dans tes procédures de build ? :-)

Qu'est-ce que tu préfères : te mettre au lisp ou au templates ?
;-) (Non, je ne me mettrais pas à AWK ;-)


Je disais AWK comme ça. Les générateurs de ce genre sont
integrés dans les procédures de build. Du coup, il faut qu'ils
soit dans un langage garanti d'être présent, et plus ou moins
garanti d'être connu de quiconque qui va maintenir le code. Je
travaille prèsqu'exclusivement dans des environements Unix. En
dehors de C/C++, je ne suppose que ce qui est garanti par Posix.
(Et mon système de build, qui se base sur GNU make.) Je suppose
également que quelqu'un qui développe sur Unix connaît un peu
Posix. (Bien que... je suis parfois deçu à cet égard.)

--
James Kanze (Gabi Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34




Avatar
Franck Branjonneau
"James Kanze" écrivait:

On Feb 23, 11:06 pm, Franck Branjonneau wrote:
"James Kanze" écrivait:


[...]
Enfin, si tu as une borne supérieur au nombre d'éléments, il y a
toujours le surcharge. Un peu pénible à entrée (surtout si la
borne supérieur est élevée. (Je me vois mal taper une centaine
de constructeurs, tous rigueureusement identique sauf en ce qui
concerne le nombre de paramètres. Mais un petit script d'AWK...)


Remplace ton AWK par un compilateur C++ ;-) :


Remplacer quelque chose de facilement lisible par quelque chose
que je ne comprendrais pas moi-même dans une semaine ?


C'est un argument imparable si ce n'est que moi j'écrirais un script
elisp.


Tu mets de l'elisp dans tes procédures de build ? :-)


Non. :-)

Les générateurs de ce genre sont
integrés dans les procédures de build. Du coup, il faut qu'ils
soit dans un langage garanti d'être présent, et plus ou moins
garanti d'être connu de quiconque qui va maintenir le code.


Oui, c'est pourquoi utiliser les templates, même si ce n'est pas
optimal, est une solution.

Je travaille prèsqu'exclusivement dans des environements Unix. En
dehors de C/C++, je ne suppose que ce qui est garanti par Posix.
(Et mon système de build, qui se base sur GNU make.) Je suppose
également que quelqu'un qui développe sur Unix connaît un peu
Posix. (Bien que... je suis parfois deçu à cet égard.)


J'entends bien.

--
Franck Branjonneau





1 2