2010-10-09 4 views
5

Quelle est la meilleure façon de lier une référence rvalue à un objet donné ou une copie temporaire de celui-ci?style dans la liaison d'une référence à l'objet-ou-dummy

A &&var_or_dummy = modify? static_cast<A&&>(my_A) 
         : static_cast<A&&>(static_cast<A>(my_A)); 

(Ce code ne fonctionne pas sur mon récent GCC 4.6 ... Je me souviens qu'il travaille avant, mais maintenant il renvoie toujours une copie.)

Sur la première ligne, la static_cast transforme my_A d'un lvalue à une valeur x. (C++ 0x §5.2.9/1-3) Le static_cast intérieur sur la deuxième ligne effectue la conversion lvalue-à-valeur, et l'externe obtient une valeur x à partir de cette prvalue.

Cela semble être pris en charge car la référence nommée est conditionnellement liée au temporaire par §12.2/5. La même astuce fonctionne de la même manière en C++ 03 avec une référence const.

Je peux aussi écrire la même chose moins verbeux:

A &&var_or_dummy = modify? std::move(my_A) 
         : static_cast<A&&>(A(my_A)); 

Maintenant, il est beaucoup plus courte. La première abréviation est discutable: move est censé signaler que quelque chose se passe à l'objet, pas un simple mélange lvalue-à-xvalue-à-lvalue. Confusément, move ne peut pas être utilisé après le : car l'appel de la fonction interrompt la liaison temporaire à référence. La syntaxe A(my_A) est peut-être plus claire que la static_cast, mais elle est techniquement équivalente à une distribution de type C.

Je peux aussi aller tout le chemin et l'écrire entièrement en style C moulages:

A &&var_or_dummy = modify? (A&&)(my_A) : (A&&)(A(my_A)); 

Après tout, si cela va être un idiome, il doit être pratique et static_cast n'est pas vraiment me protéger de toute façon - le vrai danger est de ne pas lier directement à my_A dans le cas true. D'autre part, cela est facilement dominé par le nom de type répété trois fois. Si A ont été remplacés par un gros ID de modèle moche, je voudrais vraiment un vrai raccourci.

(Notez que V est évalué qu'une seule fois malgré apparaissant cinq fois :)

#define VAR_OR_DUMMY(C, V) ((C)? \ 
    static_cast< typename std::remove_reference< decltype(V) >::type && >(V) \ 
: static_cast< typename std::remove_reference< decltype(V) >::type && > ( \ 
    static_cast< typename std::remove_reference< decltype(V) >::type >(V))) 

hackish comme les macros sont, je pense que c'est la meilleure alternative du groupe. C'est un peu dangereux car il renvoie une valeur x, il ne devrait donc pas être utilisé en dehors de l'initialisation de la référence.

Il doit y avoir quelque chose que je n'ai pas pensé ... des suggestions?

+0

Sans macro, je pense que vous pouvez supprimer une des mentions de type 'A' en utilisant' auto && var_or_dummy = ... '. Pas que ce soit beaucoup mieux ... Pour mon édification: 'VAR_OR_DUMMY' ne peut pas être implémenté en tant que modèle de fonction car le temporaire doit être lié directement à la référence rvalue et renvoyer une référence rvalue à partir d'une fonction ne fonctionnera pas, ? –

+0

@James: Puis-je utiliser 'auto' d'une façon ou d'une autre à la place de' remove_reference :: type'? ... Oui, c'est ma pensée. J'ai essayé de lui faire renvoyer une référence lvalue aussi (voir l'historique des modifications) mais d'oh, aucune fonction n'est permise. – Potatoswatter

+0

Non, je ne pense pas que 'auto' peut être utilisé à la place de' remove_reference :: type' (bien qu'en regardant ça, j'ai découvert que c'est valide: 'auto p = new auto (1);' ... 'p' a le type' int * '). –

Répondre

2

Il suffit d'éviter tout ce gâchis avec un appel de fonction supplémentaire:

void f(bool modify, A &obj) { 
    return [&](A &&obj) { 
    real(); 
    work(); 
    }(modify ? std::move(obj) : std::move(A(obj))); 
} 

Au lieu de:

void f(bool modify, A &obj) { 
    A &&var_or_dummy = /* ??? */; 
    real(); 
    work(); 
} 

Il est lambdas, lambdas, everywhere!

+0

Etes-vous sûr que le résultat de l'opérateur conditionnel peut faire référence à l'objet original dans ce cas? Le deuxième opérande est une valeur x et le troisième opérande est une prvalue. Et même si vous ajoutez un std :: move() autour de A (obj), cela ne fonctionnera pas avec GCC car GCC semble être un buggy w.r.t. l'opérateur conditionnel et les opérandes xvalue. Je n'ai pas la version la plus récente de GCC. Avez-vous testé ce code par hasard? – sellibitze

+0

@sellibitze: C'est la manière canonique d'utiliser xvalues. Le même bug avec '?:' Et xvalues ​​affecte mon code ... Je m'attendrais à ce que cela soit corrigé avant longtemps. (Et cela a fonctionné dans le passé.) – Potatoswatter

+1

+1, c'est aussi la manière canonique d'utiliser des expressions lambda. Cela aurait tendance à tester la pureté fonctionnelle de C++ 0x ... quelle différence fait-il * vraiment * que ce bloc possède son propre cadre de pile? – Potatoswatter

2

Je vois deux problèmes avec votre approche.

Vous comptez sur le comportement

int i = 0; 
int& j = true?  i :  i; 
int&& k = true? move(i) : move(i); 
assert(&i == &j); // OK, Guaranteed since C++98 
assert(&i == &k); // Does this hold as well? 

La norme actuelle projet N3126 contient 5.16/4:

Si les deuxième et troisième opérandes [à l'opérateur conditionnel] sont glvalues ​​de la même catégorie de valeur et le même type, le résultat est de cette catégorie de type et la valeur

ce qui me fait penser que les deux affirmations ci-dessus devraient tenir. Mais en utilisant GCC 4.5.1, le second échoue. Je crois que c'est un bug du GCC.

De plus, vous comptez sur le compilateur pour prolonger la durée de vie de l'objet temporaire y fait référence à l'exemple suivant:

A func(); 

A&& x = func();     // #1 
A&& y = static_cast<A&&>(func()); // #2 

x ne sera pas une référence boiteuse mais je ne suis pas sûr à propos de y. Je pense que la règle sur l'extension de la durée de vie des temporaires est seulement supposée s'appliquer dans les cas où les expressions d'initialisation sont pure rvalues. Au moins, cela simplifierait grandement la mise en œuvre. Aussi, GCC semble être d'accord avec moi sur celui-ci. GCC ne prolonge pas la durée de vie de l'objet temporaire A dans le second cas. Ce serait un problème de référence pendante dans votre approche .

Mise à jour: Selon 12.2/5, les durées de vie des objets temporaires sont supposées être étendues dans les deux cas, n ° 1 et n ° 2. Aucun des points de la liste d'exceptions ne semble s'appliquer ici. Encore une fois, GCC semble être bogué à cet égard.

Une solution facile pour votre problème serait:

vector<A> tempcopy; 
if (!modify) tempcopy.push_back(myA); 
A& ref = modify ? myA : tempcopy.back(); 

Alternativly, vous pouvez utiliser un boost :: scoped_ptr au lieu d'un vecteur.

+0

Oui ... (Dans votre premier exemple, 'static_cast' est nécessaire au lieu de' move' qui est une fonction.) Le FCD ne fait pas de distinction entre les références lvalue et les références rvalue dans la clause d'extension de vie, donc je suis sûr que les références sont semblables à cet égard. +1 pour la solution de contournement ... dommage que C++ 0x n'ait pas importé Boost Optional, qui est le meilleur outil pour ce job. – Potatoswatter

+0

@Potatoswatter: Je suis confus. Pourquoi pensez-vous que cela fasse une différence dans le * premier * exemple, que move ou static_cast soit utilisé? Le premier exemple ne concerne pas les problèmes de durée de vie. Il s'agit de savoir si l'expression xvalue résultante fait référence à l'objet d'origine. – sellibitze

+0

@sellibitze: 12.2/5. Une idée clé est que les expressions intermédiaires sont des valeurs x, et non des références, de sorte que le phrasé «référence est liée» se réfère exclusivement à des objets nommés. (Et la valeur de retour d'une fonction déclarée avec le type de référence, qui est un cas exclu.) – Potatoswatter

0

Le problème de la sécurité xvalue peut être contourné en fournissant une alternative à l'utilisation dans les expressions. Les questions sont complètement différentes, maintenant, nous ne sommes pas veulent un résultat xvalue et peut utiliser une fonction:

template< typename T > 
T &var_or_dummy(bool modify, T &var, T &&dummy = T()) { 
    if (modify) return var; 
    else return dummy = var; 
} 

    maybe_get_result(arg, var_or_dummy(want_it, var)); 

Maintenant, le type doit être constructible par défaut, et le mannequin est toujours construit. La copie est évaluée conditionnellement. Je ne pense pas que je veuille vraiment faire face à un code qui en fait trop.

Boost Optional peut aider un peu; il ne nécessite que CopyConstructible T:

template< typename T > 
T &var_or_dummy(bool modify, T &var, 
       boost::optional<T> &&dummy = boost::optional<T>()) { 
    if (modify) return var; 
    else return dummy = var; 
} 

en option est utile, mais il a un certain chevauchement avec les syndicats C++ 0x. Ce n'est pas trop difficile à réimplémenter.

template< class T > 
struct optional_union { 
    bool valid; 
    union storage { 
     T obj; // union of one non-POD member simply reserves storage 

     storage() {} // uh, what could the constructor/destructor possibly do?? 
     ~storage() {} 
    } s; 

    optional_union() : valid(false) {} 
    optional_union &operator=(T const &in) { 
     new(&s.obj) T(in); // precondition: ! valid 
     valid = true; 
     return *this; 
    } 
    ~optional_union() 
     { if (valid) s.obj.~T(); } 
}; 

template< typename T > 
T &var_or_dummy(bool modify, T &var, 
       optional_union<T> &&dummy = optional_union<T>()) { 
    if (modify) return var; 
    else return (dummy = var).s.obj; 
} 

La classe optional_union est seulement suffisant pour cette application ... évidemment, il pourrait être étendu beaucoup.

+1

Puisque vous avez déjà mis "le travail" dans une fonction séparée pour votre premier exemple, vous pourriez tout aussi bien écrire 'if (modify) {work (myA); } else {Une copie = myA; travail (copie); } ';-) – sellibitze

+0

@sellibitze: L'objet est d'éviter la duplication de code. – Potatoswatter

Questions connexes