Template concept checking, vieille version

Le
Marc Boyer
Bonjour,

bon, en attendant que la proposition de Gaby passe jusque dans
les compilateurs, j'aurait une question sur la vérification
de contrainte actuellement.

Bon, classiquement, j'ai ma classe template
template <class T> class Foo;
qui ne fonctionne que si T possède certaines opérations (disons ++).

Ce que je faisais (et que j'avais du repiquer quelque part):

template <class T>
class isIncrementable{
static void constraints(){
T makeT(); // Definition pour accéder à un T sans se soucier
// du comment
makeT()++; // Test d'existence de ++ sur T
}
public isIncrementable(){
if (false) { // Jamais exécuté
constraints(); // Mais doit compiler
}
}
}

Et à l'usage
template <class T>
class Foo {
public Foo(){
isIncrementable<T>();
// ou alors
isIncrementable<T>::constraints();
}
}

Mais sur la FAQ de BS, je vois quasiment la même chose
mais avec quelques variations:
http://www.research.att.com/~bs/bs_faq2.html#constraints
static void constraints(T t) {
t++;
}
public isIncrementable(){
void(*p)(T) = constraints;
}

Donc, je m'interroge sur les nuances, et pourquoi passer
par un pointeur de fonction ?
Ce que je vois, c'est qu'il n'y a plus de hack de
makeT(). Mais y-a-t-il un autre différence qui m'a
échappée ? Et surtout, en quoi cela n'impose-t-il pas
d''avoir un constructeur de copie ? Ou est-ce que je
devrais plutôt écrire
static void constraints(T& t) {t++;}

Je pose la question car j'ai du code avec
la vieille version, et je me demande s'il est utile
de passer à la nouvelle

Marc Boyer
--
Dans "Les misérables", Victor Hugo opposait émeute et insurrection.
L'émeute est le moment chaotique de destruction. L'insurrection,
au contraire, est le moment qui projette politiquement dans l'avenir
un groupe qui a conscience de lui-même et qui veut construire
quelque chose. (Pierre Rosanvallon, Libération 21/11/05)

  • Partager ce contenu :
Vos réponses
Trier par : date / pertinence
Théo
Le #268844
Marc Boyer wrote:
Bonjour,

bon, en attendant que la proposition de Gaby passe jusque dans
les compilateurs, j'aurait une question sur la vérification
de contrainte actuellement.


pas de réponse à ce message depuis une semaine ; les fêtes ont
commencées ?


Bon, classiquement, j'ai ma classe template
template <class T> class Foo;
qui ne fonctionne que si T possède certaines opérations (disons ++).

Ce que je faisais (et que j'avais du repiquer quelque part):

template <class T>
class isIncrementable{
static void constraints(){
T makeT(); // Definition pour accéder à un T sans se soucier
// du comment


ici, il vaut mieux écrire (pour que le T renvoie puisse être
considéré comme lvalue) :

T& makeT();

pour s'en convaincre, change le test ci-dessous en ++makeT();
et teste isIncrementable<int>();


makeT()++; // Test d'existence de ++ sur T
}
public isIncrementable(){
if (false) { // Jamais exécuté
constraints(); // Mais doit compiler
}
}
}

Et à l'usage
template <class T>
class Foo {
public Foo(){
isIncrementable<T>();
// ou alors
isIncrementable<T>::constraints();
}
}

Mais sur la FAQ de BS, je vois quasiment la même chose
mais avec quelques variations:
http://www.research.att.com/~bs/bs_faq2.html#constraints
static void constraints(T t) {
t++;
}
public isIncrementable(){
void(*p)(T) = constraints;
}

Donc, je m'interroge sur les nuances, et pourquoi passer
par un pointeur de fonction ?


c'est plus court pour le même résultat et...


Ce que je vois, c'est qu'il n'y a plus de hack de
makeT().


...remplacé par la présence de l'argument t de constraints


Mais y-a-t-il un autre différence qui m'a
échappée ?


non


Et surtout, en quoi cela n'impose-t-il pas
d''avoir un constructeur de copie ?


parce qu'il n'y a jamais création d'objet ;)

dans les codes ci-dessus, le compilo ne fait que vérifier
la syntaxe et la résolution (possible) des appels et il
n'y a aucun appel effectif.

1.

dans le code suivant , corps de méthode ou de procédure :
{
T makeT();
//...
}
makeT est seulement déclaré ; le compilateur se moque de
savoir si le type T est constructible.

Idem pour la définition d'une méthode ou procédure où ce
type apparaît en argument :
constraints(T t)
{
//...
}


2.

dans ton code, "if (false)" assure que le bloc de code est
mort donc sans effet (sauf les vérifications sus-mentionnées) ;
tu as écrit qqch comme :
int main() {
if (false) {
int foo(); // foo n'apparaît nulle part ailleurs
foo() + 1; // ça ressemble à un appel à foo mais
// cet appel n'a jamais lieu
}
}
qui compile et linke sans problème ;)

dans le code de BS, c'est plus flagrant car constraints n'est
jamais appelé
void(*p)(T) = constraints;


3.

en revanche, si un code non-mort contenait qqch comme :
constraints(makeT())
alors, là, le compilateur irait vérifier que T est bien copiable
pour que le passage d'argument soit valide


Ou est-ce que je
devrais plutôt écrire
static void constraints(T& t) {t++;}


ça serait équivalent (en mieux car t serait alors lvalue)


Je pose la question car j'ai du code avec
la vieille version, et je me demande s'il est utile
de passer à la nouvelle...


alors ?


Marc Boyer


Marc Boyer
Le #268783
Le 03-12-2005, Théo
Marc Boyer wrote:
Bonjour,

bon, en attendant que la proposition de Gaby passe jusque dans
les compilateurs, j'aurait une question sur la vérification
de contrainte actuellement.


pas de réponse à ce message depuis une semaine ; les fêtes ont
commencées ?


Visiblement, il y a des sujets plus accrocheurs que d'autres.

Ce que je faisais (et que j'avais du repiquer quelque part):

template <class T>
class isIncrementable{
static void constraints(){
T makeT(); // Definition pour accéder à un T sans se soucier
// du comment


ici, il vaut mieux écrire (pour que le T renvoie puisse être
considéré comme lvalue) :

T& makeT();


OK. C'est d'ailleurs logique.

[SNIP mon code]
Mais sur la FAQ de BS, je vois quasiment la même chose
mais avec quelques variations:
http://www.research.att.com/~bs/bs_faq2.html#constraints
static void constraints(T t) {
t++;
}
public isIncrementable(){
void(*p)(T) = constraints;
}

Donc, je m'interroge sur les nuances, et pourquoi passer
par un pointeur de fonction ?


c'est plus court pour le même résultat et...


OK.

Ce que je vois, c'est qu'il n'y a plus de hack de
makeT().


...remplacé par la présence de l'argument t de constraints


Mais y-a-t-il un autre différence qui m'a
échappée ?


non


Ouf.

Et surtout, en quoi cela n'impose-t-il pas
d''avoir un constructeur de copie ?


parce qu'il n'y a jamais création d'objet ;)

dans les codes ci-dessus, le compilo ne fait que vérifier
la syntaxe et la résolution (possible) des appels et il
n'y a aucun appel effectif.


OK, en fait il ne fera se test que si on appelle
un jour la fonction.

1.

dans le code suivant , corps de méthode ou de procédure :
{
T makeT();
//...
}
makeT est seulement déclaré ; le compilateur se moque de
savoir si le type T est constructible.

Idem pour la définition d'une méthode ou procédure où ce
type apparaît en argument :
constraints(T t)
{
//...
}


2.

dans ton code, "if (false)" assure que le bloc de code est
mort donc sans effet (sauf les vérifications sus-mentionnées) ;
tu as écrit qqch comme :
int main() {
if (false) {
int foo(); // foo n'apparaît nulle part ailleurs
foo() + 1; // ça ressemble à un appel à foo mais
// cet appel n'a jamais lieu
}
}
qui compile et linke sans problème ;)

dans le code de BS, c'est plus flagrant car constraints n'est
jamais appelé
void(*p)(T) = constraints;

3.

en revanche, si un code non-mort contenait qqch comme :
constraints(makeT())
alors, là, le compilateur irait vérifier que T est bien copiable
pour que le passage d'argument soit valide


OK

Ou est-ce que je
devrais plutôt écrire
static void constraints(T& t) {t++;}


ça serait équivalent (en mieux car t serait alors lvalue)


Je pose la question car j'ai du code avec
la vieille version, et je me demande s'il est utile
de passer à la nouvelle...


alors ?


Je vais garder l'ancienne version: je ne vois pas
tellement d'avantages dans la nouvelle: elle simplifie
un peu la tache de celui qui écrit des contraintes
(mais bon, c'est moi), mais elle est plus difficile
à comprendre et elle impose d'écrire deux fonctions
au lieu d'une.
A moins que j'encapsule l'une dans l'autre...

Dans le contexte présenté par BS, c'est en effet
simple:
isCopyable<int,float>();
mais pour des concepts plus complexes (métier) se
pose la question de la documentation du concept.
Pour le moment, j'utilise le pattern suivant:
template <class T>
class IncrementableConcept {
public:
IncrementableConcept operator++();
static void constraints(){
T& makeT(); // Declaration
++makeT(); // usage
}
}

Avec Doxygen, je peux documenter le fait qu'il
faille un ++, et si l'utilisateur veut en plus
faire la vérif sur son type, il fait
IncrementableConcept<SonType>::constraints();

Le pb de la notation
isIncrementable<SonType>();
c'est que Doxygen va dire qu'il faut un constructeur
par défaut (à moins que je dise à Doxygen justement
de ne rien faire...)

Bon, je vais réfléchir. Merci.

Marc Boyer
--
Entre le fort et le faible, c'est la liberte qui opprime et le droit
qui libere. Henri Lacordaire, Dominicain


Théo
Le #269605
Marc Boyer wrote:
...
Bon, je vais réfléchir. Merci.


et peut-être lire :
http://www.boost.org/libs/concept_check/concept_check.htm
dont :
http://www.boost.org/libs/concept_check/creating_concepts.htm

Marc Boyer


théo

Marc Boyer
Le #269600
Le 06-12-2005, Théo
Marc Boyer wrote:
...
Bon, je vais réfléchir. Merci.


et peut-être lire :
http://www.boost.org/libs/concept_check/concept_check.htm
dont :
http://www.boost.org/libs/concept_check/creating_concepts.htm


Merci pour les infos.
Ceci dit, la question de la documentation n'est pas couverte.

Marc Boyer
--
Entre le fort et le faible, c'est la liberte qui opprime et le droit
qui libere. Henri Lacordaire, Dominicain


Gabriel Dos Reis
Le #272603
Marc Boyer
| Bonjour,
|
| bon, en attendant que la proposition de Gaby passe jusque dans
| les compilateurs, j'aurait une question sur la vérification
| de contrainte actuellement.
|
| Bon, classiquement, j'ai ma classe template
| template <class T> class Foo;
| qui ne fonctionne que si T possède certaines opérations (disons ++).
|
| Ce que je faisais (et que j'avais du repiquer quelque part):
|
| template <class T>
| class isIncrementable{
| static void constraints(){
| T makeT(); // Definition pour accéder à un T sans se soucier
| // du comment
| makeT()++; // Test d'existence de ++ sur T
| }
| public isIncrementable(){
| if (false) { // Jamais exécuté
| constraints(); // Mais doit compiler
| }
| }
| }
|
| Et à l'usage
| template <class T>
| class Foo {
| public Foo(){
| isIncrementable<T>();
| // ou alors
| isIncrementable<T>::constraints();

cette version appelle la fonction, donc potentiellement éxécutée.

| }
| }
|
| Mais sur la FAQ de BS, je vois quasiment la même chose
| mais avec quelques variations:
| http://www.research.att.com/~bs/bs_faq2.html#constraints
| static void constraints(T t) {
| t++;
| }
| public isIncrementable(){
| void(*p)(T) = constraints;

cette version n'exécute pas la fonction constraints.

-- Gaby
Poster une réponse
Anonyme