Tout d'abord, les «qualificatifs de référence pour * ceci» ne sont qu'une «déclaration marketing». Le type de *this
ne change jamais, voir le bas de cet article. Il est cependant beaucoup plus facile de le comprendre avec cette formulation.
Ensuite, le code suivant choisit la fonction à appeler en fonction du qualificatif ref du "paramètre d'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
}
Production:
$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp
$ ./a.out
lvalue object
rvalue object
Le tout est fait pour vous permettre de profiter du fait que l'objet auquel la fonction est appelée est une rvalue (temporaire sans nom, par exemple). Prenez le code suivant comme exemple supplémentaire:
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);
}
};
Cela peut être un peu artificiel, mais vous devriez avoir l'idée.
Notez que vous pouvez combiner les qualificatifs cv ( const
et volatile
) et les qualificatifs ref ( &
et &&
).
Remarque: De nombreuses citations standard et une explication de la 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 la norme C ++ (la partie expliquant pourquoi la réponse de @ Nicol est fausse se trouve en bas, si vous êtes seulement intéressé par cela).
La fonction qui sera appelée est déterminée par un processus appelé résolution de surcharge . Ce processus est assez compliqué, donc nous ne toucherons que la partie qui est importante pour nous.
Tout d'abord, il est important de voir comment fonctionne la résolution de surcharge pour les fonctions membres:
§13.3.1 [over.match.funcs]
p2 L'ensemble des fonctions candidates peut contenir des fonctions membres et non membres à résoudre par rapport à la même liste d'arguments. Pour que les listes d'arguments et de paramètres soient comparables dans cet ensemble hétérogène, une fonction membre est considérée comme ayant un paramètre supplémentaire, appelé paramètre d'objet implicite, qui représente l'objet pour lequel la fonction membre a été appelée . [...]
p3 De même, le cas échéant, le contexte peut construire une liste d'arguments qui contient un argument d'objet implicite pour désigner l'objet à opérer.
Pourquoi devons-nous même comparer les fonctions membres et non membres? Surcharge de l'opérateur, c'est pourquoi. Considère ceci:
struct foo{
foo& operator<<(void*); // implementation unimportant
};
foo& operator<<(foo&, char const*); // implementation unimportant
Vous voudriez certainement que ce qui suit appelle la fonction gratuite, 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 le soi-disant jeu de surcharge. Pour rendre la résolution moins compliquée, la partie en gras de la citation standard existe. De plus, c'est le bit important pour nous (même clause):
p4 Pour les fonctions membres non statiques, le type du paramètre d'objet implicite est
où X
est la classe dont la fonction est membre et cv est la qualification cv sur la déclaration de fonction membre. [...]
p5 Pendant la résolution de surcharge, [...] le paramètre d'objet implicite [...] conserve son identité puisque les conversions sur l'argument correspondant doivent respecter ces règles supplémentaires:
aucun objet temporaire ne peut être introduit pour contenir l'argument du paramètre d'objet implicite; et
aucune conversion définie par l'utilisateur ne peut être appliquée pour obtenir une correspondance de type avec elle
[...]
(Le dernier bit signifie simplement que vous ne pouvez pas tricher avec une résolution de surcharge basée sur des conversions implicites de l'objet auquel une fonction membre (ou opérateur) est appelée.)
Prenons le premier exemple en haut de cet article. Après la transformation susmentionnée, l'ensemble de surcharge ressemble à 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 d'arguments, contenant un argument d'objet implicite , est comparée à la liste de paramètres de chaque fonction contenue dans l'ensemble de surcharge. Dans notre cas, la liste d'arguments contiendra uniquement cet argument d'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 que toutes les surcharges de l'ensemble ont été testées, il n'en reste qu'une, la résolution de 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
A noter toutefois que, si nous ne fourni aucune ref-qualification (et en tant que telle ne surcharge la fonction), qui f1
serait correspondre à une rvalue (encore §13.3.1
):
p5 [...] Pour les fonctions membres non statiques déclarées sans qualificatif ref , une règle supplémentaire s'applique:
- même si le paramètre d'objet implicite n'est pas
const
qualifié, une valeur r peut être liée au paramètre tant que, à tous autres égards, l'argument peut être converti en type de paramètre d'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, pourquoi la réponse de @ Nicol est au moins en partie fausse. Il dit:
Notez que cette déclaration change le type de *this
.
C'est faux, *this
c'est toujours une valeur l:
§5.3.1 [expr.unary.op] p1
L' *
opérateur unaire effectue une 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 valeur l 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 this
- clé est une expression de valeur 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
est X*
. [...]