2017-10-12 24 views
1

Tenir compte:Pourquoi cette conversion définie par l'utilisateur n'est-elle pas effectuée?

template<typename T> 
struct Prop 
{ 
    T value; 
    operator T() { return value; } 
}; 

int main() 
{ 
    Prop<float> p1 { 5 }; 
    Prop<std::vector<float>> p2 { { 1, 2, 3 } }; 

    float f1 = p1;    // Works fine 
    float f2_1_1 = p2.value[0]; // Works fine 
    float f2_1_2 = p2[0];  // Doesn't compile 

    return 0; 
} 

Pourquoi ne pas la ligne marquée en tant que telle compilation? Ne devrait-il pas effectuer une conversion implicite en utilisant l'opérateur de conversion fourni dans std::vector<>, de sorte que le [] peut être trouvé?

Il y a (beaucoup) d'autres questions sur ce site qui demandent des variations sur cette question, mais je ne pouvais pas en trouver une qui s'applique ici. Est-ce que cela a à voir avec std::vector étant un modèle?

+2

L'expression 'p2 [0]' est en fait la même chose que 'p2.operator [] (0)' (qui devrait être possible de déduire des messages d'erreur). Et la classe 'Prop' n'a pas de fonction' operator [] '. –

+0

Donc le problème est que p2 [0] est considéré comme une expression, non? Parce que si p2 devait être considéré comme une expression en soi, la conversion en std :: vector serait effectuée, et alors .operator [] (0) serait appelé, non? Il existe donc un moyen pratique de faire en sorte que Prop se comporte comme je le souhaite, c'est-à-dire un conteneur transparent pour une valeur de type T, avec certains attributs (car dans mon code réel, Prop a des membres supplémentaires, bien sûr). – Roel

+0

La manière habituelle d'encapsuler des données de ce type est de surcharger l'opérateur d'accès membre '->' ou l'opérateur de déréférencement '*'. Ensuite, vous pouvez faire quelque chose comme 'p2-> à (0)', ou '(* p2) [0]'. C'est plus encombrant, mais c'est quand même mieux qu'un static_cast. –

Répondre

4

conversions implicites ne sont pas considérés comme des objets d'appels de fonctions membres, y compris la surcharge de l'opérateur de l'indice. Considérez les conséquences si cela était autorisé: Chaque fois qu'une fonction membre non déclarée est appelée comme ceci, le compilateur devrait comprendre tous les types auxquels l'objet peut être converti (notez que tout autre type non apparenté pourrait avoir un constructeur de conversion), et vérifier si cela a déclaré la fonction membre manquant. Sans parler de la confusion que cela pourrait avoir pour le lecteur du code (la conversion peut être évidente dans votre cas d'opérateur de conversion, mais pas dans le cas du constructeur de conversion, et pour autant que je sache, ils ne sont pas traités différemment).

Ainsi est-il un moyen notationally pratique d'obtenir Prop de se comporter de la façon dont je veux

Vous devez définir une fonction membre pour chacune des fonctions de membre du vecteur que vous voulez passer passe par transparence.Voici l'opérateur comme un exemple indice:

auto operator[](std::size_t pos) { 
    return value[pos]; 
} 
auto operator[](std::size_t pos) const { 
    return value[pos]; 
} 

Le problème est bien sûr que toutes les fonctions membres enveloppées doivent être explicitement déclarés. Un autre problème est les arguments dont le type dépend de T. Par exemple, vector::operator[] utilise vector::size_type, qui peut ne pas être défini pour tous T que vous pourriez utiliser (certainement pas pour float). Ici nous faisons un compromis et utilisons std::size_t.

Une manière moins laborieuse de créer une telle enveloppe "transparente" est l'héritage. Un modèle héritant publiquement aurait automatiquement toutes les fonctions membres du parent et serait implicitement convertible en celui-ci et pourrait être référé par des pointeurs et des références de type parent. Cependant, la transparence d'une telle approche est un peu problématique principalement parce que ~vector n'est pas virtuel.

héritage privé permet même emballage que votre approche membre, mais avec une syntaxe beaucoup plus agréable:

template<typename T> 
struct Prop : private T 
{ 
    using T::operator[]; 
    using T::T; 
}; 

Notez que l'approche de l'héritage vous empêche d'utiliser les types fondamentaux T. En outre, il rend la conversion implicite impossible (même avec l'opérateur de conversion), donc vous ne pouvez pas utiliser Prop comme T dans les fonctions libres qui attendent T.


PS. Notez que votre opérateur de conversion renvoie une valeur, donc le vecteur doit être copié, ce qui peut être indésirable. Envisager de fournir des versions de référence à la place:

operator T&&()&&   { return *this; } 
operator T&()&    { return *this; } 
operator const T&() const& { return *this; } 
+0

Cela a du sens, merci. – Roel

+0

L'héritage non public, et la publication des parties souhaitées de l'interface avec 'using', pourrait être une solution à l'héritage. – Angew

+0

"Le problème avec cette approche est, bien sûr, que vous ne pouvez pas envelopper n'importe quel type, mais seulement ceux qui fournissent les fonctions enveloppées." Pas assez. Puisque le wrapper est un template, les fonctions ne seront pas instanciées à moins d'être appelées. Donc vous pouvez * wrapper * les types qui ne supportent pas [], vous ne pouvez pas appliquer '[]' au wrapper dans ce cas. – Angew

1

de la même manière que l'un des

p2.size(); 
p2.begin(); 
p2.push_back(24); 
// etc. 

ne font pas de sens pour compiler

également

p2[0]; 

qui est équivalent à

p2.operator[](0); 

n » t faire sens à compiler \


Si vous voulez ce comportement (c.-à-d. pour Prop<T> à emprunterT membres) il existe une proposition C++ par Bjarne pour ajouter l'opérateur point à la langue. Je travaillerais de la même manière que l'opérateur -> fonctionne pour les pointeurs intelligents. AFAIR il y avait beaucoup de controverse et donc je ne retiendrais pas mon souffle pour ça.

A bit of background for the operator dot proposal—Bjarne Stroustrup

Operator Dot (R3) - Bjarne Stroustrup, Gabriel Dos Rei

Smart References through Delegation: An Alternative to N4477's Operator Dot - Hubert Tong, Faisal Vali

Alternatives to operator dot - Bjarne Stroustrup