Supongo que no, pero me gustaría confirmarlo. ¿Tiene algún uso const Foo&&
dónde Foo
está un tipo de clase?
Supongo que no, pero me gustaría confirmarlo. ¿Tiene algún uso const Foo&&
dónde Foo
está un tipo de clase?
Respuestas:
En ocasiones son útiles. El propio borrador de C ++ 0x los usa en algunos lugares, por ejemplo:
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
Las dos sobrecargas anteriores aseguran que las otras funciones ref(T&)
y cref(const T&)
no se unan a rvalues (que de otra manera sería posible).
Actualizar
Acabo de verificar el estándar oficial N3290 , que desafortunadamente no está disponible públicamente, y tiene en 20.8 objetos de función [function.objects] / p2:
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
Luego verifiqué el borrador más reciente posterior a C ++ 11, que está disponible públicamente, N3485 , y en 20.8 Objetos de función [function.objects] / p2 todavía dice:
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
const T&&
utiliza algún otro lugar ?
const T&&
evita que alguien use tontamente los argumentos de plantilla explícitos del formulario ref<const A&>(...)
. Ese no es un argumento muy fuerte, pero el costo de const T&&
más T&&
es bastante mínimo.
La semántica de obtener una referencia const rvalue (y no para =delete
) es para decir:
El siguiente caso de uso podría haber sido en mi humilde opinión un buen caso de uso para la referencia de rvalue a const , aunque el lenguaje decidió no adoptar este enfoque (ver la publicación SO original ).
Por lo general, sería aconsejable usar make_unique
y make_shared
, pero ambos unique_ptr
y shared_ptr
se pueden construir a partir de un puntero sin formato. Ambos constructores obtienen el puntero por valor y lo copian. Ambos permiten (es decir, en el sentido de: no previenen ) un uso continuo del puntero original que se les pasó en el constructor.
El siguiente código se compila y da como resultado double free :
int* ptr = new int(9);
std::unique_ptr<int> p { ptr };
// we forgot that ptr is already being managed
delete ptr;
Ambos unique_ptr
y shared_ptr
podrían evitar lo anterior si sus constructores relevantes esperaran obtener el puntero sin formato como un valor constante , por ejemplo, para unique_ptr
:
unique_ptr(T* const&& p) : ptr{p} {}
En cuyo caso, el código doble libre anterior no se compilaría, pero lo siguiente sí:
std::unique_ptr<int> p1 { std::move(ptr) }; // more verbose: user moves ownership
std::unique_ptr<int> p2 { new int(7) }; // ok, rvalue
Tenga en cuenta que ptr
aún se podría usar después de que se movió, por lo que el error potencial no desapareció por completo. Pero si el usuario debe llamar a std::move
un error de este tipo, caería en la regla común de: no use un recurso que se movió.
Uno puede preguntar: OK, pero ¿por qué T*
const&& p
?
La razón es simple, para permitir la creación de un unique_ptr
puntero constante . Recuerde que la referencia const rvalue es más genérica que solo la referencia rvalue, ya que acepta tanto const
y non-const
. Entonces podemos permitir lo siguiente:
int* const ptr = new int(9);
auto p = std::unique_ptr<int> { std::move(ptr) };
esto no funcionaría si esperáramos solo una referencia de rvalue (error de compilación: no se puede vincular const rvalue a rvalue ).
De todos modos, es demasiado tarde para proponer tal cosa. Pero esta idea presenta un uso razonable de una referencia rvalue a const .
Están permitidos e incluso las funciones se clasifican según const
, pero como no puede moverse desde el objeto const referido por const Foo&&
, no son útiles.
const T&, T&, const T&&, T&&
Además de std :: ref , la biblioteca estándar también usa la referencia const rvalue en std :: as_const para el mismo propósito.
template <class T>
void as_const(const T&&) = delete;
También se usa como valor de retorno en std :: opcional cuando se obtiene el valor envuelto:
constexpr const T&& operator*() const&&;
constexpr const T&& value() const &&;
Así como en 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;
Esto es presumiblemente para mantener la categoría de valor así como la consistencia de la envoltura al acceder al valor envuelto.
Esto hace una diferencia si las funciones calificadas por ref const rvalue se pueden llamar en el objeto empaquetado. Dicho esto, no conozco ningún uso para las funciones calificadas const rvalue ref.
No puedo pensar en una situación en la que esto sea útil directamente, pero podría usarse indirectamente:
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);
}
La T en g es T constante, entonces f 's x es una T constante &&.
Es probable que esto dé como resultado un error de comilla en f (cuando intenta mover o usar el objeto), pero f podría tomar un rvalue-ref para que no se pueda llamar en lvalues, sin modificar el rvalue (como en el demasiado simple ejemplo anterior).
const&&
son muy importantes, aunque no dice por qué: youtube.com/watch?v=JhgWFYfdIho#t=54m20s