Tout d'abord, l'expression "qualificatifs de référence pour *ceci" n'est qu'une "déclaration de marketing". Le type de *this
ne change jamais, voir le bas de ce post. C'est beaucoup plus facile à comprendre avec cette formulation.
Ensuite, le code suivant choisit la fonction à appeler en se basant sur l'attribut ref-qualifier du "paramètre objet implicite" de la fonction † :
// t.cpp
#include <iostream>
struct test{
void f() &{ std::cout << "lvalue object\n"; }
void f() &&{ std::cout << "rvalue object\n"; }
};
int main(){
test t;
t.f(); // lvalue
test().f(); // rvalue
}
Sortie :
$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp
$ ./a.out
lvalue object
rvalue object
Tout cela est fait pour vous permettre de profiter du fait que l'objet sur lequel la fonction est appelée est une rvalue (temporaire sans nom, par exemple). Prenons le code suivant comme autre exemple :
struct test2{
std::unique_ptr<int[]> heavy_resource;
test2()
: heavy_resource(new int[500]) {}
operator std::unique_ptr<int[]>() const&{
// lvalue object, deep copy
std::unique_ptr<int[]> p(new int[500]);
for(int i=0; i < 500; ++i)
p[i] = heavy_resource[i];
return p;
}
operator std::unique_ptr<int[]>() &&{
// rvalue object
// we are garbage anyways, just move resource
return std::move(heavy_resource);
}
};
C'est peut-être un peu artificiel, mais vous devriez comprendre l'idée.
Notez que vous pouvez combiner les cv-qualificateurs ( const
y volatile
) y ref-qualificateurs ( &
y &&
).
Note : Beaucoup de citations standard et d'explication de résolution de surcharge après ici !
† Pour comprendre comment cela fonctionne, et pourquoi la réponse de @Nicol Bolas est au moins partiellement fausse, nous devons creuser un peu dans la norme C++ (la partie expliquant pourquoi la réponse de @Nicol est fausse se trouve en bas, si cela ne vous intéresse que cela).
La fonction qui va être appelée est déterminée par un processus appelé résolution des surcharges . Ce processus est assez compliqué, nous n'aborderons donc que la partie qui est importante pour nous.
Tout d'abord, il est important de voir comment fonctionne la résolution des surcharges pour les fonctions membres :
§13.3.1 [over.match.funcs]
p2 L'ensemble des fonctions candidates peut contenir à la fois des fonctions membres et non membres à résoudre par rapport à la même liste d'arguments. Ainsi, les listes d'arguments et de paramètres sont comparables au sein de cet ensemble hétérogène, une fonction membre est considérée comme ayant un paramètre supplémentaire, appelé paramètre implicite de l'objet, qui représente l'objet pour lequel la fonction membre a été appelée. . [...]
p3 De même, lorsque cela est approprié, le contexte peut construire une liste d'arguments qui contient un argument implicite de l'objet pour désigner l'objet à traiter.
Pourquoi avons-nous besoin de comparer les fonctions des membres et des non-membres ? La surcharge d'opérateurs, voilà pourquoi. Considérez ceci :
struct foo{
foo& operator<<(void*); // implementation unimportant
};
foo& operator<<(foo&, char const*); // implementation unimportant
Vous voudriez certainement que le suivant appelle la fonction libre, n'est-ce pas ?
char const* s = "free foo!\n";
foo f;
f << s;
C'est pourquoi les fonctions membres et non membres sont incluses dans ce qu'on appelle l'ensemble des surcharges. Pour rendre la résolution moins compliquée, la partie en gras de la citation standard existe. De plus, c'est la partie importante pour nous (même clause) :
p4 Pour les fonctions membres non statiques, le type du paramètre implicite de l'objet est
où X
est la classe dont la fonction est membre et cv est la qualification cv de la déclaration de la fonction membre. [...]
p5 Pendant la résolution de la surcharge [...] [l]e paramètre objet implicite [...] conserve son identité puisque les conversions sur l'argument correspondant doivent obéir à ces règles supplémentaires :
-
aucun objet temporaire ne peut être introduit pour contenir l'argument du paramètre implicite de l'objet ; et
-
aucune conversion définie par l'utilisateur ne peut être appliquée pour obtenir une correspondance de type avec celui-ci
[...]
(La dernière partie signifie simplement que vous ne pouvez pas tromper la résolution de surcharge basée sur des conversions implicites de l'objet sur lequel une fonction membre (ou un opérateur) est appelée).
Prenons le premier exemple en haut de cet article. Après la transformation susmentionnée, le jeu de surcharges ressemble à quelque chose comme ceci :
void f1(test&); // will only match lvalues, linked to 'void test::f() &'
void f2(test&&); // will only match rvalues, linked to 'void test::f() &&'
Ensuite, la liste des arguments, contenant un argument implicite de l'objet est comparée à la liste de paramètres de chaque fonction contenue dans l'ensemble de surcharge. Dans notre cas, la liste des arguments ne contiendra que l'argument objet. Voyons à quoi cela ressemble :
// first call to 'f' in 'main'
test t;
f1(t); // 't' (lvalue) can match 'test&' (lvalue reference)
// kept in overload-set
f2(t); // 't' not an rvalue, can't match 'test&&' (rvalue reference)
// taken out of overload-set
Si, après avoir testé toutes les surcharges de l'ensemble, il n'en reste qu'une, la résolution de la surcharge a réussi et la fonction liée à cette surcharge transformée est appelée. Il en va de même pour le deuxième appel à 'f' :
// second call to 'f' in 'main'
f1(test()); // 'test()' not an lvalue, can't match 'test&' (lvalue reference)
// taken out of overload-set
f2(test()); // 'test()' (rvalue) can match 'test&&' (rvalue reference)
// kept in overload-set
Notez cependant que, si nous n'avions pas fourni de ref-qualifier (et en tant que tel n'a pas surchargé la fonction), que f1
serait correspond à une rvalue (toujours §13.3.1
):
p5 [...] Pour les fonctions membres non statiques déclarées sans un ref-qualifier une règle supplémentaire s'applique :
- même si le paramètre implicite de l'objet n'est pas
const
-qualifié, une rvalue peut être liée au paramètre tant que, à tous autres égards, l'argument peut être converti au type du paramètre objet implicite.
struct test{
void f() { std::cout << "lvalue or rvalue object\n"; }
};
int main(){
test t;
t.f(); // OK
test().f(); // OK too
}
Maintenant, voyons pourquoi la réponse de @Nicol est au moins partiellement fausse. Il dit :
Notez que cette déclaration change le type de *this
.
C'est faux, *this
es toujours une lvalue :
§5.3.1 [expr.unary.op] p1
L'unaire *
L'opérateur effectue indirection l'expression à laquelle il est appliqué doit être un pointeur vers un type d'objet ou un pointeur vers un type de fonction. et le résultat est une lvalue se référant à l'objet ou à la fonction vers laquelle l'expression pointe.
§9.3.2 [class.this] p1
Dans le corps d'une fonction membre non statique (9.3), le mot-clé this
est une expression prvalue dont la valeur est l'adresse de l'objet pour lequel la fonction est appelée. Le type de this
dans une fonction membre d'une classe X
es X*
. [...]