Uma função get_from_variant em C ++
Esta é a pergunta de acompanhamento de Uma função recursive_transform para vários tipos Iteráveis aninhados com implementação std :: variant em C ++ . Como a resposta de G. Sliepen mencionou, deixar apenas a operação de transformação recursiva para recursive_transform()pode ser uma ideia melhor. Como resultado, a implementação da recursive_transformfunção é mantida da seguinte forma. Além disso, as declarações futuras foram removidas.
template<class T, class _Fn> requires is_iterable<T>
static inline T recursive_transform(const T input, _Fn func)
{
T returnObject = input;
std::transform(input.begin(), input.end(), returnObject.begin(), func);
return returnObject;
}
template<class T, class _Fn> requires is_iterable<T> && is_element_iterable<T>
static inline T recursive_transform(const T input, _Fn func)
{
T returnObject = input;
std::transform(input.begin(), input.end(), returnObject.begin(),
[func](const auto& element)
{
return recursive_transform(element, func);
}
);
return returnObject;
}
No entanto, ainda quero lidar com a estrutura composta com intervalos e std::variant, como std::vector<std::variant<double>>. Uma nova função get_from_variantsurge em minha mente a fim de focar nas operações com essas coisas.
template<typename T_variant, typename T>
static inline auto get_from_variant(T_variant input_variant)
{
T return_val;
std::visit([&](auto&& arg)
{
return_val = static_cast<T>(arg);
return arg;
},
input_variant);
return return_val;
}
Os testes desta get_from_variantfunção:
int main()
{
// get_from_variant function test
std::variant<double> testNumber = 1;
std::cout << get_from_variant<decltype(testNumber), double>(testNumber);
// The usage of recursive_transform function and get_from_variant function
std::variant<double> variant_number = 3.14;
std::vector<decltype(variant_number)> testVector1;
testVector1.push_back(variant_number);
testVector1.push_back(variant_number);
testVector1.push_back(variant_number);
std::cout << get_from_variant<std::variant<double>, double>(recursive_transform(testVector1, [](auto x){ return get_from_variant<std::variant<double>, double>(x) + 1; }).at(0)) << std::endl;
return 0;
}
Todas as sugestões são bem-vindas.
Para qual pergunta é um seguimento?
Uma função recursive_transform para vários tipos Iterable aninhada com implementação std :: variant em C ++
Que mudanças foram feitas no código desde a última pergunta?
Para lidar com a estrutura composta com intervalos e
std::variant, destd::vector<std::variant<double>>uma forma melhor, uma nova funçãoget_from_variantfoi criada.Por que uma nova revisão está sendo solicitada?
Na minha opinião, não tenho certeza se o design da função
get_from_varianté bom? A ideia ou o uso é bom ou não? Qualquer comentário é bem vindo.
Respostas
Não tenho acompanhado este tópico desde o início, então estou mais confuso do que você espera que os leitores estejam a esta altura. Seria uma boa ideia fornecer um exemplo compilável completo a cada vez - mesmo apenas como um link Godbolt, se quiser manter o foco da pergunta em alguma pequena parte do código.
Na verdade, eu prefiro ver um link Godbolt ( além de ver o código na pergunta como você fez corretamente), pois isso me poupa do trabalho de colar seu código em Godbolt sozinho. :) Aqui está um link para o seu código: Godbolt .
std::variant<double> testNumber = 1;
Isso não compila em C ++ 20. Costumava? Se for assim, caramba, essa é uma grande quebra de API para C ++ ... mas não é seu problema. De qualquer forma, mude para 1.0e recompile.
template<typename T_variant, typename T>
static inline auto
Perca o static inline. Os modelos são efetivamente embutidos por definição, e você não quer que esse modelo seja estático - você não quer forçar cada unidade de tradução a manter sua própria cópia exclusiva (no caso de não ser otimizada pelo embutido).
Eu não sou fã de Giraffe_case. Os nomes dos parâmetros do modelo devem ser curtos e CamelCase; aqui eu recomendo V.
Seu std::visitlambda tem um inútil return arg;. Na verdade, toda esta função deve se parecer mais com
template<class V, class T>
auto get_from_variant(V input) {
return std::visit([&](auto&& arg) {
return static_cast<T>(arg);
}, input);
}
Com a sujeira removida, temos células cerebrais livres para focar no próximo nível de pedantismo: você está argencaminhando referência ( auto&&), mas não está realmente encaminhando para o static_cast. Talvez devêssemos usar static_cast<T>(static_cast<decltype(arg)>(arg))aqui, de modo que se argfor uma referência de rvalue, ele será movido para To construtor de?
Mas espere; argvai não ser uma referência rvalue, porque estamos visitando um lvalue input! Portanto, talvez não devêssemos esperar modificar o argque visitamos - poderíamos considerá-lo const auto& arg. Mas se nós não esperamos para modificar input, talvez ele deve ser tomado por- sim, espere um minuto, por que estamos fazendo uma cópia inputaqui? Basta considerá-lo por referência constante para começar!
template<class V, class T>
auto get_from_variant(const V& input) {
return std::visit([](const auto& arg) {
return static_cast<T>(arg);
}, input);
}
Deixei cair o [&]do lambda, já que não requer nenhuma captura.
Devemos também olhar os parâmetros do template para get_from_variant. Vpode ser deduzido e Tnão pode; sempre sempre faz sentido colocar os parâmetros não dedutíveis primeiro.
template<class T, class V>
auto get_from_variant(const V& input) {
return std::visit([](const auto& arg) {
return static_cast<T>(arg);
}, input);
}
Agora nosso principal driver se parece com este :
std::variant<double> testNumber = 1.0;
std::cout << get_from_variant<double>(testNumber);
std::vector testVector1 = {
std::variant<double>(3.14),
std::variant<double>(3.14),
std::variant<double>(3.14),
};
std::cout << get_from_variant<double>(
recursive_transform(testVector1, [](const auto& x){
return get_from_variant<double>(x) + 1;
}).at(0)
) << std::endl;
Enquanto isso, em recursive_transform, você cometeu um erro de digitação: const T inputquando você quis dizer const T& input. Você pode executar grep mecanicamente para esses erros de digitação, e deve!
Novamente, remova
static inlinedos modelos.O nome
_Fné reservado para a implementação; apenas useF.Copiar
funcpara o lambda não é necessário; você deve usar[&]como padrão para cada lambda que escrever (a menos que, como acima, você consiga usar o plain[]).Honestamente, a menos que você tenha raiva de seguir expressões STL, apenas passe o callback
Fpor referência const e evite copiá-lo. Existe um lugar em C ++ para retornos de chamada mutáveis e com estado, mastransformnão é esse lugar.Seu caso base é mais complicado do que precisa ser. Vamos consertar isso.
Juntando tudo:
template<class T, class F>
T recursive_transform(const T& input, const F& f) {
return f(input);
}
template<class T, class F> requires is_iterable<T>
T recursive_transform(const T& input, const F& f) {
T returnObject = input;
std::transform(input.begin(), input.end(), returnObject.begin(),
[&](const auto& element) {
return recursive_transform(element, f);
}
);
return returnObject;
}
E então, realmente me parece que usar std::transformaqui é um exagero: ele lê input duas vezes , uma para fazer a cópia e novamente para fazer a transformação. Suponha que apenas abramos o código, assim.
template<class T, class F> requires is_iterable<T>
T recursive_transform(const T& input, const F& f) {
T output = input;
for (auto&& elt : output) {
elt = recursive_transform(elt, f);
}
return output;
}
Claro que poderia usar C ++ 20 Ranges para fazer algo como este :
template<class T, class F> requires is_iterable<T>
T recursive_transform(const T& input, const F& f) {
auto transformed = input | std::views::transform([&](auto&& x) {
return recursive_transform(x, f);
});
return T(transformed.begin(), transformed.end());
}
É mais lento para compilar e gera código maior - mas pode realmente ser mais rápido em tempo de execução, se T::value_typefor caro para copiar, porque estamos eliminando as atribuições de cópia T::value_type- estamos apenas construindo diretamente no local.