O seguinte conceito do C++20 - The Complete Guide (adaptado de http://wg21.link/p0870 ), proíbe conversões de estreitamento. Por exemplo, float
para int
, como em 1.9f
→ 1
.
template <typename From, typename To>
concept ConvertibleWithoutNarrowing = requires (From&& from) {
{ std::type_identity_t<To[]>{std::forward<From>(from)}} -> std::same_as<To[1]>;
};
O livro usa isso para uma coleção C que não deve ter conversões de restrição ao adicionar dados:
template<typename C, typename T>
requires ConvertsWithoutNarrowing<T, typename C::value_type>
void add(C& collection, const T& val) {…}
// Usage:
std::vector<int> vec_i;
add(vec_i, 1); // OK
add(vec_i, 1.3); // Does not compile.
Entendi a ideia geral por trás do conceito, mas o que o [1]
na última parte, std::same_as<To[1]>;
, faz?
TL;DR
[1]
especifica explicitamente que o tamanho do quearray
está sendo usado para comparação tem que ser, bem,1
. E usar um array economiza muito trabalho.Explicação adicional:
1. Mas por que usar um
array
em primeiro lugar?Restrições C++ no estreitamento do tipo de array
Ao criar uma matriz, C++ requer que o tipo de elementos usados para inicializar a matriz não envolva nenhuma conversão de estreitamento
Então se você tentar compilar
Você obterá um erro do tipo
narrowing conversion from double to int
. A inicialização falha porque você não pode atribuir diretamente um double a um array int sem truncar ou converter explicitamente o valor.2. Sobre aquele "requisito curto e complicado"
O conceito fornecido
Garante que um valor de tipo
From
pode ser convertido para tipoTo
sem perder informações (ou seja, nenhuma conversão de estreitamento é permitida). O truque está em alavancar as regras mais rígidas em torno da inicialização de array e conversões de estreitamento para evitar a necessidade de escrever verificações explicitamente para esses casos (por exemplo, integral para ponto flutuante, ponto flutuante para integral).Sem ela, seria necessário muito mais envolvimento para atingir um resultado semelhante: esse conceito explodiria em:
E isso cobre apenas o caso de tipos aritméticos! Para uma implementação equivalente genuína sem depender de restrições de inicialização de array, você precisaria adicionar cada caso de estreitamento possível novamente, manualmente.
3. Outra maneira de pensar sobre conceitos: eles fazem perguntas sobre tipos
Uma maneira simples de entender o que o uso
ConvertibleWithoutNarrowing
faz é pensar nisso como "fazer" a seguinte pergunta:A resposta é "sim" se
From
puder ser convertido paraTo
sem estreitamento .Para efeito de comparação, uma possível implementação de um conceito que verifica a conversão que permite o estreitamento:
Você pode pensar nesse conceito como "perguntar":
Agora considere essas "questões" para o caso em que
From = double
eTo = int
:ConvertibleWithoutNarrowing
falha porque o tipodouble
não pode ser restringidoint
na lista do inicializador; masConvertibleWithNarrowing
passes , pois é permitido restringirdouble
paraint
fora da inicialização de uma lista - ele apenas trunca para um inteiro.