Acho que não, mas gostaria de confirmar. Existe alguma utilidade para const Foo&&
, onde Foo
está um tipo de classe?
Acho que não, mas gostaria de confirmar. Existe alguma utilidade para const Foo&&
, onde Foo
está um tipo de classe?
Respostas:
Eles são ocasionalmente úteis. O próprio rascunho C ++ 0x os usa em alguns lugares, por exemplo:
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
As duas sobrecargas acima garantem que as outras funções ref(T&)
e cref(const T&)
não se vinculem a rvalues (o que de outra forma seria possível).
Atualizar
Acabei de verificar o padrão oficial N3290 , que infelizmente não está disponível publicamente e tem em 20.8 Objetos de função [function.objects] / p2:
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
Então eu verifiquei o rascunho pós-C ++ 11 mais recente, que está disponível publicamente, N3485 , e em 20.8 Objetos de função [function.objects] / p2 ele ainda diz:
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
const T&&
é usado?
const T&&
evita que alguém use tolamente os argumentos de template explícitos do formulário ref<const A&>(...)
. Esse não é um argumento muito forte, mas o custo de const T&&
terminar T&&
é mínimo.
A semântica de obter uma referência const rvalue (e não para =delete
) é para dizer:
O seguinte caso de uso poderia ter sido IMHO um bom caso de uso para a referência rvalue para const , embora a linguagem tenha decidido não adotar essa abordagem (veja o post original do SO ).
Normalmente seria aconselhável usar make_unique
e make_shared
, mas ambos unique_ptr
e shared_ptr
podem ser construídos a partir de um ponteiro bruto. Ambos os construtores obtêm o ponteiro por valor e o copiam. Ambos permitem (ou seja, no sentido de: não evita ) um uso continuado do ponteiro original passado a eles no construtor.
O código a seguir é compilado e resulta com 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
e shared_ptr
poderiam evitar o acima se seus construtores relevantes esperassem obter o ponteiro bruto como um valor const r , por exemplo, para unique_ptr
:
unique_ptr(T* const&& p) : ptr{p} {}
Nesse caso, o código duplo livre acima não seria compilado, mas o seguinte:
std::unique_ptr<int> p1 { std::move(ptr) }; // more verbose: user moves ownership
std::unique_ptr<int> p2 { new int(7) }; // ok, rvalue
Observe que ptr
ainda pode ser usado depois de movido, então o bug em potencial não desapareceu totalmente. Mas se o usuário for obrigado a chamar std::move
tal bug, cairia na regra comum de: não use um recurso que foi movido.
Pode-se perguntar: OK, mas por que T*
const&& p
?
A razão é simples, para permitir a criação de um unique_ptr
ponteiro const . Lembre-se de que a referência const rvalue é mais genérica do que apenas a referência rvalue , pois aceita ambos const
e non-const
. Portanto, podemos permitir o seguinte:
int* const ptr = new int(9);
auto p = std::unique_ptr<int> { std::move(ptr) };
isso não funcionaria se esperássemos apenas uma referência de rvalue (erro de compilação: não é possível vincular const rvalue a rvalue ).
De qualquer forma, é tarde demais para propor tal coisa. Mas essa ideia apresenta um uso razoável de uma referência rvalue para const .
Eles são permitidos e até mesmo funções classificadas com base em const
, mas como você não pode mover do objeto const referido por const Foo&&
, eles não são úteis.
const T&, T&, const T&&, T&&
Além de std :: ref , a biblioteca padrão também usa a referência const rvalue em std :: as_const para o mesmo propósito.
template <class T>
void as_const(const T&&) = delete;
Ele também é usado como valor de retorno em std :: optional ao obter o valor empacotado:
constexpr const T&& operator*() const&&;
constexpr const T&& value() const &&;
Bem como em 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;
Isso presumivelmente é para manter a categoria de valor, bem como a constância do wrapper ao acessar o valor embalado.
Isso faz diferença se as funções qualificadas const rvalue ref podem ser chamadas no objeto empacotado. Dito isso, não conheço nenhum uso para as funções qualificadas const rvalue ref.
Não consigo pensar em uma situação em que isso seja útil diretamente, mas pode ser usado indiretamente:
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);
}
OT em g é T const, então f 's x é um T const &&.
É provável que isso resulte em um erro de compilação em f (quando ele tenta mover ou usar o objeto), mas f pode tomar um rvalue-ref de forma que não possa ser chamado em lvalues, sem modificar o rvalue (como no muito simples exemplo acima).
const&&
são muito importantes, embora não diga por quê: youtube.com/watch?v=JhgWFYfdIho#t=54m20s