Por que std :: swap não está marcado como constexpr antes do C ++ 20?

14

No C ++ 20, std::swaptorna-se uma constexprfunção.

Eu sei que a biblioteca padrão realmente ficou para trás da linguagem na marcação de coisas constexpr, mas em 2017, ela <algorithm>estava praticamente consexprada como muitas outras coisas. No entanto - std::swapnão era. Lembro-me vagamente de algum defeito de linguagem estranho que impedia essa marcação, mas esqueço os detalhes.

Alguém pode explicar isso de forma sucinta e clara?

Motivação: É necessário entender por que pode ser uma má idéia marcar uma std::swap()função semelhante constexprno código C ++ 11 / C ++ 14.

einpoklum
fonte

Respostas:

11

O problema do idioma estranho é o CWG 1581 :

A cláusula 15 [especial] é perfeitamente clara que funções especiais de membro são definidas implicitamente apenas quando são usadas por odr. Isso cria um problema para expressões constantes em contextos não avaliados:

struct duration {
  constexpr duration() {}
  constexpr operator int() const { return 0; }
};

// duration d = duration(); // #1
int n = sizeof(short{duration(duration())});

O problema aqui é que não podemos definir implicitamente constexpr duration::duration(duration&&)neste programa; portanto, a expressão na lista de inicializadores não é uma expressão constante (porque invoca uma função constexpr que não foi definida); portanto, o inicializador entre chaves contém uma conversão restrita , então o programa está mal formado.

Se descomentarmos a linha 1, o construtor de movimentação é implicitamente definido e o programa é válido. Essa ação assustadora à distância é extremamente infeliz. As implementações divergem nesse ponto.

Você pode ler o restante da descrição do problema.

Uma resolução para esse problema foi adotada no P0859 em Albuquerque em 2017 (após o envio do C ++ 17). Esse problema foi um bloqueador para ambos poderem ter um constexpr std::swap(resolvido em P0879 ) e um constexpr std::invoke(resolvido em P1065 , que também possui exemplos de CWG1581), ambos para C ++ 20.


O exemplo mais simples de entender aqui, na minha opinião, é o código do relatório de bug do LLVM apontado na P1065:

template<typename T>
int f(T x)
{
    return x.get();
}

template<typename T>
constexpr int g(T x)
{
    return x.get();
}

int main() {

  // O.K. The body of `f' is not required.
  decltype(f(0)) a;

  // Seems to instantiate the body of `g'
  // and results in an error.
  decltype(g(0)) b;

  return 0;
}

CWG1581 é sobre quando as funções de membro constexpr são definidas e a resolução garante que elas sejam definidas apenas quando usadas. Após P0859, o acima está bem formado (o tipo de bé int).

Desde std::swape std::invokeambos têm de contar com a verificação de funções membro (movimento construção / atribuição no primeiro e o operador de chamada / chamadas substitutos no último), ambos dependia da resolução desta questão.

Barry
fonte
Então, por que o CWG-1581 impede / torna indesejável marcar uma função de troca como constexpr?
einpoklum 13/03
3
@einpoklum swap requer std::is_move_constructible_v<T> && std::is_move_assignable_v<T>é true. Isso não pode acontecer se as funções membro especiais ainda não forem geradas.
NathanOliver 13/03
@ NathanOliver: Adicionei isso à minha resposta.
einpoklum 13/03
5

O motivo

(devido a @NathanOliver)

Para permitir uma constexprfunção de troca, você deve verificar - antes de instanciar o modelo para essa função - se o tipo de troca é móvel-construtível e móvel-atribuível. Infelizmente, devido a um defeito de idioma resolvido apenas no C ++ 20, você não pode verificar isso, pois as funções-membro relevantes ainda não foram definidas, no que diz respeito ao compilador.

A cronologia

  • 2016: Antony Polukhin envia a proposta P0202 , para marcar todas as<algorithm> funções como constexpr.
  • O grupo de trabalho principal do comitê padrão discute o defeito CWG-1581 . Esta questão tornou problemático ter constexpr std::swap()e tambémconstexpr std::invoke() - veja a explicação acima.
  • 2017: Antônio revisa sua proposta algumas vezes para excluirstd::swap e algumas outras construções, e isso é aceito no C ++ 17.
  • 2017: Uma resolução para o problema CWG-1581 é enviada como P0859 e aceita pelo comitê padrão em 2017 (mas após o C ++ 17).
  • Final de 2017: Antony envia uma proposta complementar, P0879 , para fazerstd::swap() consexpr após a resolução do CWG-1581.
  • 2018: a proposta complementar é aceita (?) No C ++ 20. Como Barry aponta, o mesmo ocorre com a std::invoke()correção constexpr .

Seu caso específico

Você pode usar a constexprtroca se não verificar a capacidade de construção e a capacidade de movimentação, mas verificar diretamente se há algum outro recurso de tipos que garanta isso em particular. por exemplo, apenas tipos primitivos e sem classes ou estruturas. Ou, teoricamente, você pode renunciar às verificações e apenas lidar com quaisquer erros de compilação que possa encontrar e com a alternância de comportamentos inadequados entre os compiladores. De qualquer forma, não substitua std::swap()por esse tipo de coisa.

einpoklum
fonte