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::swap
e std::invoke
ambos 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.
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.O motivo
(devido a @NathanOliver)
Para permitir uma
constexpr
funçã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
<algorithm>
funções comoconstexpr
.constexpr std::swap()
e tambémconstexpr std::invoke()
- veja a explicação acima.std::swap
e algumas outras construções, e isso é aceito no C ++ 17.std::swap()
consexpr após a resolução do CWG-1581.std::invoke()
correção constexpr .Seu caso específico
Você pode usar a
constexpr
troca 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 substituastd::swap()
por esse tipo de coisa.fonte