Czy odwołania do wartości r do const mają jakieś zastosowanie?

Odpowiedzi:

78

Czasami są przydatne. Sam szkic C ++ 0x używa ich w kilku miejscach, na przykład:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

Powyższe dwa przeciążeniu zapewnia, że druga ref(T&)i cref(const T&)funkcje nie wiążą się rvalues (które w innym przypadku byłyby możliwe).

Aktualizacja

Właśnie sprawdziłem oficjalny standard N3290 , który niestety nie jest publicznie dostępny i ma w 20.8 Function objects [function.objects] / p2:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

Następnie sprawdziłem najnowszą wersję roboczą po C ++ 11, która jest publicznie dostępna, N3485 , aw 20.8 Function objects [function.objects] / p2 nadal mówi:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
Howard Hinnant
źródło
Patrząc na cppreference , wydaje się, że już tak nie jest. Jakieś pomysły, dlaczego? Jakieś inne miejsca const T&&są używane?
Pubby
58
Dlaczego trzykrotnie zawarłeś ten sam kod w odpowiedzi? Zbyt długo próbowałem znaleźć różnicę.
typ1232,
9
@ typ1232: Wygląda na to, że zaktualizowałem odpowiedź prawie 2 lata po udzieleniu odpowiedzi, z powodu obaw w komentarzach, że funkcje, o których mowa, już się nie pojawiają. Zrobiłem kopiowanie / wklejanie z N3290 iz ostatniej wówczas wersji roboczej N3485, aby pokazać, że funkcje nadal się pojawiały. W tamtym czasie używanie kopiowania / wklejania było najlepszym sposobem, aby upewnić się, że więcej oczu niż moje może potwierdzić, że nie przeoczyłem drobnych zmian w tych podpisach.
Howard Hinnant,
1
@kevinarpe: „Inne przeciążenia” (nie pokazane tutaj, ale w standardzie) pobierają referencje l-wartości i nie są usuwane. Pokazane tutaj przeciążenia lepiej pasują do rvalues ​​niż przeciążenia, które pobierają odwołania do l-wartości. Więc argumenty rvalue wiążą się z przeciążeniami pokazanymi tutaj, a następnie powodują błąd czasu kompilacji, ponieważ te przeciążenia są usuwane.
Howard Hinnant
1
Użycie opcji const T&&zapobiega głupiemu używaniu jawnych argumentów szablonu formularza ref<const A&>(...). To nie jest strasznie mocny argument, ale koszt const T&&przekroczenia T&&jest dość minimalny.
Howard Hinnant
6

Semantyka uzyskiwania pliku odniesienia do wartości stałej r (a nie dla=delete) polega na stwierdzeniu:

  • nie obsługujemy operacji na lvalues!
  • mimo to nadal kopiujemy , ponieważ nie możemy przenieść przekazanego zasobu lub nie ma rzeczywistego znaczenia dla jego „przenoszenia”.

Następujący przypadek użycia mógł być IMHO dobrym przypadkiem użycia odniesienia rvalue do const , chociaż język zdecydował się nie stosować tego podejścia (patrz oryginalny post SO ).


Przypadek: inteligentny konstruktor wskaźników z surowego wskaźnika

Zwykle byłoby wskazane użycie make_uniquei make_shared, ale oba unique_ptri shared_ptrmogą być skonstruowane z surowego wskaźnika. Oba konstruktory pobierają wskaźnik według wartości i kopiują go. Oba pozwalają (tj. W sensie: nie uniemożliwiają ) kontynuacji używania oryginalnego wskaźnika przekazanego im w konstruktorze.

Poniższy kod kompiluje się i daje podwójne wolne :

int* ptr = new int(9);
std::unique_ptr<int> p { ptr };
// we forgot that ptr is already being managed
delete ptr;

Oba unique_ptri shared_ptrmogłyby zapobiec powyższemu, gdyby ich odpowiednie konstruktory oczekiwały, że otrzymają surowy wskaźnik jako stałą rwartość , np. Dla unique_ptr:

unique_ptr(T* const&& p) : ptr{p} {}

W takim przypadku podwójny wolny kod powyżej nie skompilowałby się, ale następujący:

std::unique_ptr<int> p1 { std::move(ptr) }; // more verbose: user moves ownership
std::unique_ptr<int> p2 { new int(7) };     // ok, rvalue

Zwróć na to uwagę ptr można go nadal używać po przeniesieniu, więc potencjalny błąd nie zniknął całkowicie. Ale jeśli użytkownik jest zobowiązany do wywołania std::movetakiego błędu, podlegałoby powszechnej zasadzie: nie używaj zasobu, który został przeniesiony.


Można zapytać: OK, ale dlaczego T* const&& p ?

Powód jest prosty, aby umożliwić tworzenie unique_ptr ze wskaźnika stałej . Pamiętaj, że odwołanie do wartości stałej r jest bardziej ogólne niż samo odniesienie do wartości, ponieważ akceptuje zarówno consti non-const. Możemy więc zezwolić na:

int* const ptr = new int(9);
auto p = std::unique_ptr<int> { std::move(ptr) };

to nie poszłoby, gdybyśmy oczekiwali tylko odniesienia do wartości r (błąd kompilacji: nie można powiązać wartości stałej r z wartością r ).


W każdym razie jest już za późno, aby coś takiego zaproponować. Ale ten pomysł przedstawia rozsądne użycie odniesienia wartości r do const .

Amir Kirsh
źródło
Byłem wśród ludzi z podobnymi pomysłami, ale nie miałem wsparcia, by iść naprzód.
Red.Wave
"auto p = std :: unique_ptr {std :: move (ptr)};" nie kompiluje się z błędem „Odjęcie argumentu szablonu klasy nie powiodło się”. Myślę, że powinno to być „unique_ptr <int>”.
Zehui Lin
4

Są dozwolone, a nawet funkcje są uszeregowane w oparciu o const, ale ponieważ nie można przejść z obiektu const, do którego się odwołuje const Foo&&, nie są one przydatne.

Gene Bushuyev
źródło
Co dokładnie masz na myśli, mówiąc o uwadze „ranking”? Chyba ma to coś wspólnego z rozwiązywaniem przeciążenia?
fredoverflow
Dlaczego nie można było przenieść się z const rvalue-ref, skoro dany typ ma znacznik ruchu, który przyjmuje const rvalue-ref?
Fred Nurk
6
@FredOverflow, ranking przeciążenia jest następujący:const T&, T&, const T&&, T&&
Gene Bushuyev,
2
@Fred: Jak się poruszasz bez modyfikowania źródła?
fredoverflow
3
@Fred: Zmienne składowe danych lub być może przenoszenie dla tego hipotetycznego typu nie wymaga modyfikowania elementów członkowskich danych.
Fred Nurk
2

Oprócz std :: ref , biblioteka standardowa również używa odwołania const rvalue w std :: as_const w tym samym celu.

template <class T>
void as_const(const T&&) = delete;

Jest również używany jako wartość zwracana w std :: optional podczas pobierania opakowanej wartości:

constexpr const T&& operator*() const&&;
constexpr const T&& value() const &&;

Jak również w std :: get :

template <class T, class... Types>
constexpr const T&& get(const std::variant<Types...>&& v);
template< class T, class... Types >
constexpr const T&& get(const tuple<Types...>&& t) noexcept;

Jest to przypuszczalnie w celu zachowania kategorii wartości, a także stałości opakowania podczas uzyskiwania dostępu do opakowanej wartości.

To robi różnicę, czy funkcja const rvalue ref-qualified można wywołać na opakowanym obiekcie. To powiedziawszy, nie znam żadnych zastosowań funkcji kwalifikowanych const rvalue ref.

eerorika
źródło
1

Nie przychodzi mi do głowy sytuacja, w której byłoby to przydatne bezpośrednio, ale można by to wykorzystać pośrednio:

template<class T>
void f(T const &x) {
  cout << "lvalue";
}
template<class T>
void f(T &&x) {
  cout << "rvalue";
}

template<class T>
void g(T &x) {
  f(T());
}

template<class T>
void h(T const &x) {
  g(x);
}

T w g jest T const, więc f 's x jest T const &&.

Prawdopodobnie skutkuje to błędem kompilacji w f (gdy próbuje przenieść lub użyć obiektu), ale f może przyjąć rvalue-ref, tak że nie można go wywołać na lvalues, bez modyfikowania rvalue (jak w zbyt prostym przykład powyżej).

Fred Nurk
źródło