« No matching function » à cause d'un passage d'argument par référence

Le
Olivier Miakinen
Bonjour,

J'ai pour tâche de porter quelques milliers de lignes de code C++ de
Windows (Visual Studio 2005) vers Linux (gcc 4.1.2). Je précise que
je ne suis pas vraiment un programmeur C++ : je connais surtout le C.

Apparemment, le compilateur de Windows est beaucoup plus laxiste que
gcc, même en ajoutant les flags -fpermissive -ffriend-injection à
ce dernier. Je tombe sur un problème que je pense avoir à peu près
identifié, mais j'aurais besoin d'aide pour le corriger ou le
contourner.

Voici un bout de code simplifié montrant le problème. J'espère que je
n'ai pas trop simplifié.

class Context {

}

class Foo {
const std::string myString(Contexte & ctx) { }
}

class Bar {
std::string _fooBar;

Bar(const Foo & foo) : _fooBar(foo.myString(ContexteVide())) {};
}

La fonction ContexteVide() retourne un objet de classe Contexte. Le
compilateur Windows est parfaitement satisfait avec ce code (du moins
j'espère ne pas l'avoir trop charcuté), mais gcc me dit « no matching
function for call to Foo::myString(Contexte) ».

L'analyse que je fais, c'est qu'il refuse de passer le Contexte par
référence du fait que c'est une valeur pour laquelle l'appelant n'a
justement aucune référence (elle est générée par la fonction
ContexteVide() ). D'ailleurs, j'ai un cas similaire où j'ai trouvé
deux contournements possibles :

Truc RetourneUnTruc() { }

Machin une_fonction(Truc & truc) { }

{

result = une_fonction(RetourneUnTruc());

}

Le premier contournement consiste à changer la définition de
une_fonction() en ajoutant 'const', ce qui est possible si le
paramètre truc n'est pas modifié par la fonction :

Machin une_fonction(const Truc & truc) { }

L'autre contournement consiste à changer l'appel en passant par
une variable intermédiaire :

{

Truc unTruc = RetourneUnTruc();
result = une_fonction(unTruc);

}

Très bien ici. Mais dans le problème du début, je ne peux pas facilement
rajouter un const à la définition de Foo::myString() parce qu'elle-même
appelle une autre fonction qui en appelle une autre, etc., sans aucun
const -- même si je suis persuadé que le paramètre n'est en fait jamais
modifié. Et je ne vois pas comment passer par une variable intermédiaire
dans la liste d'initialisation du constructeur de Bar.

Que me conseillez-vous ?

Note : la réponse RTFM est parfaitement possible, si vous avez un
pointeur vers un bon « FM » à me recommander

--
Olivier Miakinen
Vidéos High-Tech et Jeu Vidéo
Téléchargements
Vos réponses
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Alain Ketterlin
Le #26410134
Olivier Miakinen
class Context {
...
}
class Foo {
const std::string myString(Contexte & ctx) { ... }
}

J'imagine que tu as oublié un const pour myString(), cad
const std::string myString(Contexte & ctx) const { ... }
sinon la suite (foo.myString()) ne peut pas marcher.
class Bar {
std::string _fooBar;
Bar(const Foo & foo) : _fooBar(foo.myString(ContexteVide())) {};
}
La fonction ContexteVide() retourne un objet de classe Contexte. Le
compilateur Windows est parfaitement satisfait avec ce code (du moins
j'espère ne pas l'avoir trop charcuté), mais gcc me dit « no matching
function for call to Foo::myString(Contexte) ».

Oui, il considère que l'objet n'est pas une référence.
[...]
Le premier contournement consiste à changer la définition de
une_fonction() en ajoutant 'const', ce qui est possible si le
paramètre truc n'est pas modifié par la fonction :
Machin une_fonction(const Truc & truc) { ... }
L'autre contournement consiste à changer l'appel en passant par
une variable intermédiaire :
{
...
Truc unTruc = RetourneUnTruc();
result = une_fonction(unTruc);
...
}
Très bien ici. Mais dans le problème du début, je ne peux pas facilement
rajouter un const à la définition de Foo::myString() parce qu'e lle-même
appelle une autre fonction qui en appelle une autre, etc., sans aucun
const -- même si je suis persuadé que le paramètre n'est e n fait jamais
modifié.

A terme c'est surement la meilleure chose à faire, si c'est possible.
Et je ne vois pas comment passer par une variable intermédiaire dans
la liste d'initialisation du constructeur de Bar.

Le plus simple est d'utiliser un petit helper, du genre :
class Bar {
std::string _fooBar;
static std::string the_foobar(const Foo & foo)
{
Contexte ctx;
return foo.myString(ctx);
}
Bar(const Foo & foo) : _fooBar(the_foobar(foo)) {};
};
Ici the_foobar fabrique directement un std::string à partir d'un Foo.
-- Alain.
Olivier Miakinen
Le #26410143
Bonjour Alain,
Le 20/09/2016 17:50, Alain Ketterlin m'a répondu :
class Context {
...
}
class Foo {
const std::string myString(Contexte & ctx) { ... }
}

J'imagine que tu as oublié un const pour myString(), cad
const std::string myString(Contexte & ctx) const { ... }

Tu as raison. J'avais donc effectivement trop simplifié.
sinon la suite (foo.myString()) ne peut pas marcher.

Oh... parce que foo est passé en const dans l'appel du constructeur
de Bar ! C'est là que je vois que je vais devoir pratiquer pas mal
le C++ pour avoir ce genre de réflexe, je ne l'avais pas vu.
class Bar {
std::string _fooBar;
Bar(const Foo & foo) : _fooBar(foo.myString(ContexteVide())) {};
}
La fonction ContexteVide() retourne un objet de classe Contexte. Le
compilateur Windows est parfaitement satisfait avec ce code (du moins
j'espère ne pas l'avoir trop charcuté), mais gcc me dit « no matching
function for call to Foo::myString(Contexte) ».

Oui, il considère que l'objet n'est pas une référence.

C'est logique... même s'il m'a fallu deux jours pour trouver quelle
fonction convenait à Visual Studio, et pourquoi gcc ne la trouvait
pas ! Il faut dire qu'en réalité il y a une dizaine de fonctions
surchargées, avec chacune une demi-douzaine de paramètres dont des
templates dans tous les sens.
[...]
Très bien ici. Mais dans le problème du début, je ne peux pas facilement
rajouter un const à la définition de Foo::myString() parce qu'elle-même
appelle une autre fonction qui en appelle une autre, etc., sans aucun
const -- même si je suis persuadé que le paramètre n'est en fait jamais
modifié.

A terme c'est surement la meilleure chose à faire, si c'est possible.

Je suis bien d'accord que ce serait la meilleure chose à faire, mais
malheureusement ce n'est pas possible dans le temps dont je dispose.
Je me vois déjà difficilement vérifier que mon hypothèse soit toujours
vraie (à savoir qu'il n'est vraiment jamais modifié) car l'arbre
d'héritage me semble assez touffu...
Et je ne vois pas comment passer par une variable intermédiaire dans
la liste d'initialisation du constructeur de Bar.

Le plus simple est d'utiliser un petit helper, du genre :
class Bar {
std::string _fooBar;
static std::string the_foobar(const Foo & foo)
{
Contexte ctx;

Contexte ctx = ContexteVide(); // je suppose
return foo.myString(ctx);
}
Bar(const Foo & foo) : _fooBar(the_foobar(foo)) {};
};
Ici the_foobar fabrique directement un std::string à partir d'un Foo.

Ça me semble une excellente méthode, qui ne modifie qu'un seul
fichier et pas des dizaines. Merci beaucoup !
--
Olivier Miakinen
Publicité
Poster une réponse
Anonyme