2011-02-21 4 views
13

Je suis bloqué en lisant la description de std::bind dans N3225, dans la sous-section 20.8.10.1. Il dit que ce qui suit devrait imprimer 1, mais je pensais que bind est censé copier ses arguments et à ce titre, il devrait imprimer 0. Si l'on veut se référer à l'argument passé, il faut utiliser std::ref, non?Que diable fait std :: bind (x, y)?

void f(int &a) { a = 1; } 

int main() { 
    int a = 0; 
    std::bind(f, a)(); 
    std::cout << a << std::endl; 
} 

sorties GCC 0, en accord avec ce que je pensais que les choses fonctionnent. Mais N3225 dit que std::bind(f, a1) renvoie un wrapper d'appel que lorsqu'il est appelé par wrapper() appellera INVOKE(f, v1), où v1 sont a (l'argument je suis passé, autrement dit, en utilisant le paramètre entrant de binds qui est un paramètre de transfert parfait, std::forward<A1>(a1)) .

INVOKE(f, a) est défini par 20.8.2 à f(a). Ainsi, cela définit que l'appel à l'encapsuleur d'appel renvoyé passe l'argument d'origine. Qu'est-ce que je rate?

+0

http://stackoverflow.com/questions/4327474/does-perfect-forwarding-in-c0x-make-reference-wrapper-deprecated (Pas un doublon mais lié, si nous obtiendrons un "transfert parfait" en C + + 0x) – CashCow

Répondre

2

Wow, c'est confus au-delà de la croyance. Il définit v1 comme tid et il est le suivant (ti est le paramètre de liaison d'expédition parfait, et TiD est le type décroissant de ce paramètre - un tableau devient un pointeur, etc.).

tid est une lvalue de type TiD construit à partir std::forward<Ti>(ti)

Bon, je ne dis, ce tid est std::forward<Ti>(ti) et c'est une lvalue! Mais ce n'est pas ce que cela signifie vraiment. Cela signifie

tid est une lvalue de type TiD qui fait référence à un objet construit à partir de std::forward<Ti>(ti)

Il est beaucoup plus logique maintenant. Parce que si std::forward<Ti>(ti) est réellement une valeur? La "lvalue ... construite à partir de ..." est censée signifier que nous créons un nouvel objet à partir de "..." et que nous faisons référence à la lvalue.

3

Il imprime un 0 à l'heure actuelle car au moment où vous appelez std :: bind, il ne sait pas que vous voulez passer une référence. Il ne regarde pas la signature de la fonction pour voir quels types de paramètres il prend et ajuster en conséquence.

Pour le faire fonctionner correctement appeler

void f(int &a) { a = 1; } 

int main() { 
    int a = 0; 
    std::bind(f, std::ref(a))(); 
    std::cout << a << std::endl; 
} 

C++ 0x est ce qui suggère « la reliure parfaite », mais il y a un danger énorme avec ce qui pourrait mal casser le code existant en silence qui repose sur le comportement actuel. Voici un exemple très simple.

void myCallback(const std::string& str, int i); 

function< void(int) > makeCallback(const std::string & str) 
{ 
    return bind(myCallback, str, _1); 
} 

À l'heure actuelle, vous pouvez compter sur bind copie la chaîne que vous passez avec str et donc le fait qu'il sera valide venir l'invocation du rappel. Si elle utilisait "intelligemment" la "liaison parfaite" pour la stocker comme référence, elle casserait des situations comme celle-ci.

+2

L'affiche originale indiquait clairement qu'il savait utiliser 'std :: ref'. Il a demandé comment la spécification devrait être interprétée, * pas * comment faire imprimer le code 1, donc ce n'est pas une réponse. –

+0

J'ai maintenant répondu pourquoi ne pas forcer explicitement std :: ref pourrait être très dangereux. Pas sûr qu'il répond directement à la question et je ne comprends vraiment pas toutes ces choses tid ... – CashCow

5

Il dit ce qui suit devrait imprimer 1

Non, il ne dit pas que.

Si l'on veut se référer à l'argument passé, il faut utiliser std :: ref, non?

Oui.

Mais N3225 dit que std :: bind (f, a1) retourne une enveloppe d'appel que lorsque appelé par enveloppe() va appeler Invoke (f, v1), où v1 est un (l'argument que je passé en d'autres termes, en utilisant le paramètre entrant de binds qui est un paramètre de transfert parfait, std :: forward (a1)).

C'est là où vous avez tort. Les "arguments liés" que vous avez transmis à l'appel bind sont stockés sous la forme d'objets nouvellement créés de type TiD qui sont chacun construits de forward<Ti>(ti). Ceci est rendu raisonnablement clair en disant "tid est une lvalue de type TiD construite de std::forward<Ti>(ti)". En raison du traitement spécial des enveloppes de référence, il existe une "couche de transformation" supplémentaire. Voir 20.8.10.1.2/10 qui explique comment vi et Vi se rapportent à tid et TiD.

+0

Pour moi, en disant qu'une lvalue de 'TiD' est construite à partir de' forward (ti) 'signifie que' forward (ti) 'doit être une lvalue ou être convertie en' TiD & 'comme dans' (TiD &) forward (ti) 'pour être une lvalue. Je ne peux absolument pas le lire en disant qu'il doit créer un nouvel objet auquel la lvalue se réfère. Parce que dans un tel cas, le lvalue serait construit en nommant ce nouvel objet. OMI est une façon très confuse de l'affirmer. –

+0

"construction" implique "objet". Dans '(TiD &) forward (ti)' il n'y a pas de construction d'objet ayant lieu. Une référence lvalue n'est pas un type d'objet. – sellibitze

+0

"Construit" peut signifier autre chose que de créer des objets. Comparer à "programme C++ construit selon les règles de syntaxe [...]" (définition de "bien formé"). «Construire» utilisé avec «construire une lvalue» ne signifie pas créer un objet. Construire une lvalue ne peut signifier que ce qu'elle dit, pas autre chose. Le code suivant construit une lvalue à partir de l'identifiant 'x': 'int main() {int x = 0; X; } '. Dans le code suivant, * aucune lvalue n'est construite * 'int main() {int x = 0; } '. –