3

Lors de l'écriture d'une classe pour l'encapsulation d'un objet affecté par un tas, j'ai rencontré un problème de conversion de type implicite qui peut être réduit à cet exemple simple.Impossible de trouver l'opérateur via une conversion implicite en C++

Dans le code ci-dessous, la classe wrapper gère un objet affecté par le tas et convertit implicitement en une référence à cet objet. Cela permet à l'objet wrapper d'être passé comme argument à la fonction write (...) puisque la conversion implicite a lieu.

Le compilateur échoue, cependant, en essayant de résoudre l'appel à l'opérateur < < (...), sauf si une distribution explicite est faite (vérifié avec les compilateurs MSVC8.0, Intel 9.1 et gcc 4.2.1). Donc, (1) pourquoi la conversion implicite échoue-t-elle dans ce cas? (2) pourrait-il être lié à la recherche dépendant de l'argument? et (3) est-ce qu'il y a quelque chose qui peut être fait pour faire ce travail sans la distribution explicite?

#include <fstream> 

template <typename T> 
class wrapper 
{ 
    T* t; 
    public: 
    explicit wrapper(T * const p) : t(p) { } 
    ~wrapper() { delete t; } 
    operator T &() const { return *t; } 
}; 

void write(std::ostream& os) 
{ 
    os << "(1) Hello, world!\n"; 
} 

int main() 
{ 
    wrapper<std::ostream> file(new std::ofstream("test.txt")); 

    write(file); 
    static_cast<std::ostream&>(file) << "(2) Hello, world!\n"; 
    // file << "(3) This line doesn't compile!\n"; 
} 
+0

Je manque complètement la valeur de wrapper: les objets fstream créés sur la pile nettoient après eux-mêmes. Au lieu de cela, vous créez un objet fstream sur le tas, puis en utilisant wrapper pour garantir qu'il est nettoyé exactement au même endroit où il aurait été nettoyé pour commencer. –

+1

Dans la version réelle, j'utilise boost :: shared_ptr au lieu de T * afin que les instances soient partagées et implémente la sémantique nécessaire pour une instance partagée. L'exemple est grandement simplifié, et pas très réaliste, mais juste assez pour montrer le problème. –

+0

l'ensemble des conversions est limité si les arguments d'un modèle sont auto-décu. donc dans votre cas si vous auriez un template void write (basic_ostream &); vous auriez le même problème (en plus de la recherche dépendant de l'argument dans l'op << cas) –

Répondre

2

Échec car vous essayez de résoudre un opérateur de votre classe wrapper<T> qui n'existe pas. Si vous voulez travailler sans le casting, vous pouvez mettre en place quelque chose comme ceci:

template<typename X> wrapper<T> &operator <<(X &param) const { 
    return t << param; 
} 

Malheureusement, je ne sais pas d'un moyen de résoudre le type de retour au moment de la compilation. Heureusement dans la plupart des cas, c'est le même type que l'objet, y compris dans ce cas avec ostream.

EDIT: Code modifié par suggestion de dash-tom-bang. Type de retour modifié à wrapper<T> &.

+0

> pour résoudre le type de retour au moment de la compilation Cela nécessiterait des métaprogrammes de gabarit plus avancés ... plus des chemins de types (probables) plus SFINAE. – Richard

+0

Cela peut être une bonne solution de contournement, mais cela fonctionne même si l'opérateur + = n'est pas défini pour wrapper : wrapper i (new int (10)); i + = 1; // donne 11 –

+0

Le cas int peut fonctionner parce qu'il est un type intégral où la promotion/conversion de type entier se déroule plus facilement? Par exemple. Si je suis + = 'ING un int, peut-être qu'il essaye de tourner le lhs à un int aussi? J'aime cette réponse, c'est probablement la voie à suivre même si je retournerais probablement le wrapper au lieu de –

0

Vérifiez la signature de l'opérateur d'insertion ... Je pense qu'ils prennent référence ostream non-const?

Confirmed à la norme C++ 03, signature de l'opérateur de sortie char * est:

template<class charT, class traits> 
basic_ostream<charT,traits>& operator<<(basic_ostream<charT,traits>&, const charT*); 

qui ne prend en effet une référence non const. Donc, votre opérateur de conversion ne correspond pas.

Comme indiqué dans le commentaire: ce n'est pas pertinent.

Il y a plusieurs limites dans la norme concernant les conversions appliquées ... peut-être que cela nécessiterait des conversions implicites (votre opérateur, et le cast vers le type de base) quand tout au plus une devrait être appliquée.

+0

Merci, mais cela ne fait aucune différence. L'opérateur de conversion est une fonction membre const, car il ne change pas le pointeur t, mais la conversion est toujours à T &, pas const T &. C'est juste comme la différence entre 'const T *' et 'T * const'. –

+0

Je blâme pas assez de C++ récemment. – Richard

1

Le compilateur n'a pas suffisamment de contexte pour déterminer que operator& va effectuer une conversion valide. Donc, oui, je pense que cela est lié à la recherche dépendant de l'argument: Le compilateur recherche un operator<< qui peut accepter un constwrapper<std::ostream> comme premier paramètre.

1

Je pense que le problème est lié au maintien de certaines contraintes de compilation. Dans votre exemple, le compilateur doit d'abord trouver tous l'opérateur possible < <. Puis, pour chacun d'entre eux, il devrait essayer si votre objet pourrait être automatiquement converti (directement ou indirectement) à l'un des types que chaque opérateur < < peut accepter.

Ce test peut être très complexe, et je pense que c'est limité pour fournir un temps de compilation raisonnable.

1

Après quelques tests, un exemple encore plus simple identifie la source du problème. Le compilateur ne peut pas déduire l'argument de modèle T dans f2(const bar<T>&) ci-dessous de la conversion implicite de wrapper<bar<int> > à bar<int>&.

template <typename T> 
class wrapper 
{ 
    T* t; 
    public: 
    explicit wrapper(T * const p) : t(p) { } 
    ~wrapper() { delete t; } 
    operator T &() const { return *t; } 
}; 

class foo { }; 

template <typename T> class bar { }; 

void f1(const foo& s) { } 
template <typename T> void f2(const bar<T>& s) { } 
void f3(const bar<int>& s) { } 

int main() 
{ 
    wrapper<foo> s1(new foo()); 
    f1(s1); 

    wrapper<bar<int> > s2(new bar<int>()); 
    //f2(s2); // FAILS 
    f2<int>(s2); // OK 
    f3(s2); 
} 

Dans l'exemple original, std::ostream est en fait un typedef pour la classe std::basic_ostream<..> basé sur des modèles, et la même situation s'applique lorsque vous appelez la fonction operator<< basé sur un modèle.

Questions connexes