Por que o esperado <T> no LLVM implementa dois construtores para o esperado <T> &&?

8

Expected<T>é implementado em llvm / Support / Error.h. É uma união com tags que contém a Tou an Error.

Expected<T>é uma classe de modelo com o tipo T:

template <class T> class LLVM_NODISCARD Expected

Mas esses dois construtores realmente me confundem:

  /// Move construct an Expected<T> value from an Expected<OtherT>, where OtherT
  /// must be convertible to T.
  template <class OtherT>
  Expected(Expected<OtherT> &&Other,
           typename std::enable_if<std::is_convertible<OtherT, T>::value>::type
               * = nullptr) {
    moveConstruct(std::move(Other));
  }

  /// Move construct an Expected<T> value from an Expected<OtherT>, where OtherT
  /// isn't convertible to T.
  template <class OtherT>
  explicit Expected(
      Expected<OtherT> &&Other,
      typename std::enable_if<!std::is_convertible<OtherT, T>::value>::type * =
          nullptr) {
    moveConstruct(std::move(Other));
  }

Por que Expected<T>repetir duas construções para a mesma implementação? Por que não faz assim ?:

template <class OtherT>
Expected(Expected<OtherT>&& Other) { moveConstruct(std::move(Other));}
yodahaji
fonte
1
Observe a explicitpalavra
Mat
Quero saber por que as explicitpalavras-chave são importantes aqui? Alguém poderia dar um exemplo?
yodahaji

Respostas:

8

Porque esse construtor é condicionalmente explícito de acordo com a proposta. Isso significa que o construtor é explícito apenas se alguma condição for atendida (aqui, conversibilidade de Te OtherT).

O C ++ não possui um mecanismo para essa funcionalidade (algo como explicit(condition)) antes do C ++ 20. Portanto, as implementações precisam usar algum outro mecanismo, como uma definição de dois construtores diferentes - um explícito e outro a conversão - e garantir a seleção do construtor apropriado de acordo com a condição. Isso geralmente é feito via SFINAE com a ajuda de std::enable_if, onde a condição é resolvida.


Desde C ++ 20, deve haver uma versão condicional do explicitespecificador. A implementação seria muito mais fácil com uma única definição:

template <class OtherT>
explicit(!std::is_convertible_v<OtherT, T>)
Expected(Expected<OtherT> &&Other)
{
   moveConstruct(std::move(Other));
}
Daniel Langr
fonte
Obrigado pela sua resposta. Embora ainda esteja confuso, recebi muitos recursos depois de pesquisar no Google 'condicionalmente explícito'.
yodahaji
@yodahaji Observe que condicionalmente explícito não é um termo padrão. Significa simplesmente que o construtor é explícito ou está convertendo de acordo com alguma condição.
Daniel Langr 20/10/19
5

Para entender isso, devemos começar std::is_convertible. De acordo com a cppreference :

Se a definição da função imaginária To test() { return std::declval<From>(); }estiver bem formada (ou seja, std::declval<From>()pode ser convertida para o Touso de conversões implícitas, ou ambas Frome Topossivelmente vazias qualificadas para cv), fornece o valor constante do membro igual a true. Caso contrário, o valor é false. Para os fins dessa verificação, o uso de std::declvalna declaração de retorno não é considerado um uso de odr.

As verificações de acesso são executadas como se fossem de um contexto não relacionado a nenhum dos tipos. Somente a validade do contexto imediato da expressão na declaração de retorno (incluindo conversões para o tipo de retorno) é considerada.

A parte importante aqui é que ele verifica apenas conversões implícitas. Portanto, o que as duas implementações no seu OP significam é que, se OtherTé implicitamente convertível em T, então expected<OtherT>é implicitamente convertível em expected<T>. Se OtherTrequer uma conversão explícita para T, Expected<OtherT>exige uma conversão explícita para Expected<T>.

Aqui estão exemplos de elencos implícitos e explícitos e suas Expectedcontrapartes

int x;
long int y = x;              // implicit cast ok
Expected<int> ex;
Expected<long int> ey = ex;  // also ok

void* v_ptr;
int* i_ptr = static_cast<int*>(v_ptr);              // explicit cast required
Expected<void*> ev_ptr;
auto ei_ptr = static_cast<Expected<int*>>(ev_ptr);  // also required
patatahooligan
fonte
Obrigado pela sua resposta. Mas não consigo entender o significado de ' Expected<OtherT>requer um elenco explícito para Expected<T>significar'. O que significa o 'elenco explícito' aqui? Não consigo imaginar um exemplo disso.
yodahaji
Foram adicionados alguns exemplos à postagem para esclarecer o significado das transmissões explícitas. Geralmente, eles são usados ​​para evitar lançamentos implícitos acidentais quando esses poderiam introduzir bugs. Infelizmente, não posso testar o código no momento. Se você encontrar um erro de digitação / erro, informe-me e eu o corrigirei.
patatahooligan
Essa afirmação "para evitar lançamentos implícitos acidentais" responde à minha pergunta. Obrigado :)
yodahaji