C++0x rvalue reference and temporary

(zadałem odmianę tego pytania na komp.choroby weneryczne.c++ ale nie dostał odpowiedzi.)

Dlaczego wywołanie f(arg) w tym kodzie wywołuje przeciążenie const ref f?

void f(const std::string &); //less efficient
void f(std::string &&); //more efficient

void g(const char * arg)
{
     f(arg);
}

Moja intuicja mówi, że należy wybrać przeciążenie f(string &&), Ponieważ arg musi być przekonwertowane na tymczasowe bez względu na wszystko, a tymczasowe pasuje do odniesienia rvalue lepiej niż odniesienie lvalue.

Tak nie jest w GCC i MSVC (edit: dzięki: it nie występuje w GCC 4.3-4.5). W co najmniej G++ i MSVC, żaden lvalue nie wiąże się z argumentem referencyjnym rvalue, nawet jeśli jest tworzony pośredni tymczasowy. Rzeczywiście, jeśli przeciążenie const ref nie jest obecne, Kompilatory diagnozują błąd. Jednak pisanie f(arg + 0) lub f(std::string(arg)) czy wybiera przeciążenie referencji rvalue zgodnie z oczekiwaniami.

Z mojej lektury standardu C++0x wynika, że domyślna konwersja znaku const * na string powinien być brany pod uwagę przy rozważaniu, czy f(string &&) jest wykonalne, podobnie jak przy przekazywaniu argumentów const lvalue ref. Sekcja 13.3 (overload resolution) nie rozróżnia referencji rvalue refs i const w zbyt wielu miejscach. Wydaje się również, że reguła uniemożliwiająca Wiązanie lvalues z referencjami rvalue (13.3.3.1.4/3) nie powinna mieć zastosowania, jeśli istnieje tymczasowy pośredni - w końcu jest całkowicie bezpieczne przejście od tymczasowego.

Jest to:

  1. ja błędne odczytanie / błędne zrozumienie standardu, gdzie zaimplementowane zachowanie jest zamierzonym zachowaniem, i jest jakiś dobry powód, dla którego mój przykład powinien zachowywać się tak, jak robi?
  2. błąd, który w jakiś sposób wszyscy producenci kompilatorów popełnili? Czy błąd oparty na wspólnych strategiach wdrożeniowych? Czy błąd np. w GCC (gdzie ta reguła wiązania lvalue/rvalue reference została po raz pierwszy zaimplementowana), który został skopiowany przez innych dostawców?
  3. wada normy lub niezamierzona konsekwencja, czy coś, co powinno być wyjaśnione?

EDIT: mam kolejne pytanie, które jest związane: C++0x rvalue reference - lvalues-rvalue binding

Author: Community, 2010-05-01

6 answers

GCC robi to źle według FCD. FCD mówi o 8.5.3 o wiązaniu odniesienia

  • Jeśli Referencja jest referencją lvalue, a wyrażenie inicjujące jest typem [lvalue / class]...
  • w przeciwnym razie odniesienie jest odniesieniem lvalue do nieulotnego typu const (tj. cv1 jest const), lub odniesienie jest odniesieniem rvalue, a wyrażenie inicjujące jest wartością rvalue lub mA typ funkcji.

Twoja sprawa dla wywołanie std::string && nie pasuje do żadnego z nich, ponieważ inicjatorem jest lvalue. Nie dociera do miejsca, gdzie można utworzyć tymczasową wartość rvalue, ponieważ ten punkt toplevel wymaga już wartości rvalue.

Teraz, rozdzielczość przeciążenia nie używa bezpośrednio powiązania odniesienia, aby sprawdzić, czy istnieje ukryta Sekwencja konwersji. Zamiast tego jest napisane w 13.3.3.1.4/2

Gdy parametr typu reference nie jest związany bezpośrednio z wyrażeniem argumentu, Sekwencja konwersji jest wymagane do konwersji wyrażenia argumentu na podstawowy typ odniesienia zgodnie z pkt 13.3.3.1.

Tak więc, rozdzielczość przeciążenia wyłania zwycięzcę, nawet jeśli ten zwycięzca może w rzeczywistości nie być w stanie powiązać się z tym argumentem. Na przykład:

struct B { B(int) { /* ... */ } };
struct A { int bits: 1; };

void f(int&);
void f(B);
int main() { A a; f(a.bits); }

Reference binding at 8.5 zabrania bitfieldom wiązania się z referencjami lvalue. Ale rozdzielczość przeciążenia mówi, że sekwencja konwersji jest tą, która przekształca się w int, a więc udaje się, mimo że gdy połączenie jest wykonane później, połączenie jest źle ukształtowane. Stąd mój przykład bitfieldów jest źle uformowany. Gdyby miała wybrać wersję B, byłaby ona pomyślna, ale wymagała konwersji zdefiniowanej przez użytkownika.

Jednakże istnieją dwa wyjątki od tej reguły. Są to

Z wyjątkiem niejawnego parametru obiektu, dla którego patrz 13.3.1, standardowa Sekwencja konwersji nie może być utworzona, jeśli wymaga wiązania odniesienia lvalue do non-const z wartością R lub wiązania wartości rvalue odniesienie do lvalue.

Tak więc, następujące wywołanie jest ważne:

struct B { B(int) { /* ... */ } };
struct A { int bits: 1; };

void f(int&); /* binding an lvalue ref to non-const to rvalue! */
void f(B);
int main() { A a; f(1); }

I tak twój przykład wywołuje const T& wersję

void f(const std::string &);
void f(std::string &&); // would bind to lvalue!

void g(const char * arg) { f(arg); }

Jeśli jednak powiesz f(arg + 0), tworzysz wartość R, a zatem druga funkcja jest wykonalna.

 8
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-01 10:38:45

To była wada w standardowym projekcie, który przeczytałeś. Wada ta pojawiła się jako efekt uboczny niektórych chętnych edycji, aby uniemożliwić Wiązanie odniesień rvalue do wartości LV ze względów bezpieczeństwa.

Twoja intuicja ma rację. Oczywiście, nie ma nic złego w tym, aby odniesienie do rvalue odnosiło się do jakiegoś nienazwanego tymczasowego, nawet jeśli inicjalizator był wyrażeniem lvalue. W końcu do tego służą referencje rvalue. Problem, który zaobserwowałeś, został naprawiony w zeszłym roku. Nadchodzący standard będzie nakazuje, aby drugie przeciążenie zostało wybrane w twoim przykładzie, gdzie Referencja rvalue będzie odnosić się do jakiegoś tymczasowego obiektu string.

Poprawka reguły znalazła się w projekcie N3225.pdf (2010-11-27):

  • [...]
  • W Przeciwnym Razie odniesienie jest odniesieniem lvalue do nieulotnego typu const (tj. cv1 jest const), lub odniesienie jest odniesieniem rvalue , a wyrażenie inicjujące jest wartością rvalue lub mA typ funkcji . [...]
    • [...]
    • inaczej, tymczasowa z [...] powstaje [...]
        double&& rrd3 = i; // rrd3 refers to temporary with value 2.0

Ale N3225 zdaje się brakowało, aby powiedzieć, co i jest w tym przykładzie. Najnowszy projekt N3290 zawiera następujące przykłady:

        double d2 = 1.0;
        double&& rrd2 = d2; // error: copying lvalue of related type
        int i3 = 2;
        double&& rrd3 = i3; // rrd3 refers to temporary with value 2.0

Ponieważ twoja wersja MSVC została wydana zanim ten problem został rozwiązany, nadal obsługuje odwołania rvalue zgodnie ze starymi zasadami. Kolejna wersja MSVC ma zaimplementować nowe reguły referencyjne rvalue (nazwane "rvalue references 2.1" by MSVC developers) zobacz link.

 6
Author: sellibitze,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-29 13:38:49

Nie widziałem zachowania wspomnianego przez Douga w g++. g++ 4.5 i 4.4.3 wywołują f(string &&) zgodnie z oczekiwaniami, ale VS2010 wywołuje f(const string &). Której wersji g++ używasz?

 2
Author: Sumant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-01 04:57:07

Wiele rzeczy w obecnym projekcie standardu wymaga wyjaśnień, jeśli mnie pytasz. Kompilatory wciąż się rozwijają, więc trudno zaufać ich pomocy.

Wygląda na to, że twoja intuicja ma rację... Na przykład, §3.10, Nowa sekcja "Taksonomia", kategorycznie definiuje tymczasowe jako wartości R.

Problem może być taki, że specyfikacja argumentu RR jest niewystarczająca, aby wywołać utworzenie tymczasowego. §5.2.2/ 5: "Gdy parametr jest typu const reference, w razie potrzeby wprowadza się obiekt tymczasowy."To brzmi podejrzanie ekskluzywnie.

Wydaje się prześlizgnąć się przez pęknięcia ponownie w §13.3.3.1 / 6: (nacisk mój)

Jeśli typ parametru nie jest referencją, domyślna Sekwencja konwersji modeluje kopiowanie-inicjalizację parametru z wyrażenia argumentu. Domyślna Sekwencja konwersji jest wymagana do przekonwertowania argumentu wyrażenie do wartości prvalue typu parametru.

Zauważ, że copy-initialization string &&rr = "hello"; działa dobrze w GCC.

EDIT: faktycznie problem nie istnieje w mojej wersji GCC. Nadal próbuję dowiedzieć się, w jaki sposób druga standardowa konwersja sekwencji konwersji zdefiniowanej przez użytkownika odnosi się do tworzenia referencji rvalue. (Czy formacja RR jest w ogóle konwersją? A może jest to podyktowane rozrzuconymi ciekawostkami jak 5.2.2 / 5?)

 1
Author: Potatoswatter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-01 05:19:13

Spójrz na to:

Http://blogs.msdn.com/vcblog/archive/2009/02/03/rvalue-references-c-0x-features-in-vc10-part-2.aspx

Rvalue reference: overload resolution

Wygląda na to, że Twój przypadek brzmi: "lvalues zdecydowanie preferuje powiązanie z referencjami lvalue".

 0
Author: Alex F,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-01 04:53:22

Nie wiem, czy to się zmieniło w najnowszych wersjach standardu, ale kiedyś mówił coś w stylu "jeśli masz wątpliwości, nie używaj referencji rvalue". Prawdopodobnie ze względu na kompatybilność.

Jeśli chcesz semantykę move, użyj f(std::move(arg)), która działa z obydwoma kompilatorami.

 0
Author: user328543,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-01 05:06:11