Uma função get_from_variant em C ++

Oct 24 2020

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, de std::vector<std::variant<double>>uma forma melhor, uma nova função get_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

2 Quuxplusone Oct 24 2020 at 23:08

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 use F.

  • 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, mas transformnã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.