As referências de rvalue a const têm alguma utilidade?

Respostas:

78

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;
Howard Hinnant
fonte
Olhando para cppreference , parece que este não é mais o caso. Alguma ideia por quê? Qualquer outro lugar const T&&é usado?
Pubby
58
Por que você incluiu o mesmo código em sua resposta três vezes? Tentei encontrar uma diferença por muito tempo.
typ1232
9
@ typ1232: Parece que atualizei a resposta quase 2 anos depois de respondê-la, devido a preocupações nos comentários de que as funções referenciadas não apareciam mais. Copiei / colei do N3290 e do último rascunho do N3485 para mostrar que as funções ainda apareciam. Usar copiar / colar, em minha mente na época, era a melhor maneira de garantir que mais olhos do que os meus pudessem confirmar que eu não estava negligenciando algumas pequenas alterações nessas assinaturas.
Howard Hinnant
1
@kevinarpe: As "outras sobrecargas" (não mostradas aqui, mas no padrão) recebem referências de lvalue e não são excluídas. As sobrecargas mostradas aqui são uma correspondência melhor para rvalues ​​do que as sobrecargas que usam referências de lvalue. Portanto, os argumentos rvalue vinculam-se às sobrecargas mostradas aqui e, em seguida, causam um erro em tempo de compilação porque essas sobrecargas são excluídas.
Howard Hinnant,
1
O uso de 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.
Howard Hinnant
6

A semântica de obter uma referência const rvalue (e não para =delete) é para dizer:

  • não oferecemos suporte à operação para lvalues!
  • mesmo assim, ainda copiamos , porque não podemos mover o recurso passado ou porque não há nenhum significado real para "movê-lo".

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 ).


O caso: construtor de smart pointers do ponteiro bruto

Normalmente seria aconselhável usar make_uniquee make_shared, mas ambos unique_ptre shared_ptrpodem 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_ptre shared_ptrpoderiam 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 ptrainda 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::movetal 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 conste 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 .

Amir Kirsh
fonte
Eu estava entre pessoas com ideias semelhantes, mas não tive o apoio para avançar.
Red.Wave
"auto p = std :: unique_ptr {std :: move (ptr)};" não compila com o erro "falha na dedução do argumento do modelo de classe". Acho que deveria ser "unique_ptr <int>".
Zehui Lin
4

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.

Gene Bushuyev
fonte
O que exatamente você quer dizer com a observação "classificada"? Algo a ver com resolução de sobrecarga, eu acho?
fredoverflow
Por que você não poderia mover de um const rvalue-ref, se o tipo fornecido tem um ctor de movimento que leva um const rvalue-ref?
Fred Nurk
6
@FredOverflow, a classificação de sobrecarga é esta:const T&, T&, const T&&, T&&
Gene Bushuyev
2
@Fred: Como você se move sem modificar a fonte?
fredoverflow
3
@Fred: Membros de dados mutáveis, ou talvez mover para este tipo hipotético, não requer a modificação dos membros de dados.
Fred Nurk
2

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.

Eerorika
fonte
1

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).

Fred Nurk
fonte