Функция get_from_variant в C ++

Oct 24 2020

Это следующий вопрос для функции recursive_transform для различных типов, вложенных итерируемых с использованием std :: variant в C ++ . Как упоминалось в ответе Г. Слипена, лучше оставить только операцию рекурсивного преобразования recursive_transform(). В результате реализация recursive_transformфункции сохраняется в следующем виде. Более того, были удалены форвардные объявления.

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;
}

Однако я все еще хочу обрабатывать составную структуру с диапазонами и std::variant, например std::vector<std::variant<double>>. get_from_variantМне приходит в голову новая функция , чтобы сосредоточиться на операциях с этими вещами.

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;
}

Тесты этой get_from_variantфункции:

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;
}

Все предложения приветствуются.

  • На какой вопрос это продолжение?

    Функция recursive_transform для вложенных итерируемых различных типов с использованием std :: variant Реализация на C ++

  • Какие изменения были внесены в код с момента последнего вопроса?

    Чтобы обрабатывать составную структуру с диапазонами и std::variant, например std::vector<std::variant<double>>, лучше, была создана новая функция get_from_variant.

  • Почему запрашивается новый обзор?

    На мой взгляд, я не уверен, get_from_variantхорош ли дизайн функции ? Хорошая идея или использование или нет? Любые комментарии приветствуются.

Ответы

2 Quuxplusone Oct 24 2020 at 23:08

Я не слежу за этой цепочкой с самого начала, поэтому к этому моменту я смущен больше, чем вы ожидаете от читателей. Было бы неплохо, если бы вы каждый раз предоставляли полный компилируемый пример - даже просто как ссылку Godbolt, если вы хотите, чтобы вопрос был сосредоточен на каком-то небольшом фрагменте кода.

Фактически, я предпочитаю видеть ссылку Godbolt ( в дополнение к тому, чтобы видеть код в вопросе, как вы правильно сделали), поскольку это избавляет меня от необходимости вставлять ваш код в Godbolt самостоятельно. :) Вот ссылка на ваш код: Godbolt .


std::variant<double> testNumber = 1;

Это не компилируется в C ++ 20. Это было раньше? Если так, то это довольно большая проблема для API C ++ ... но не ваша проблема. В любом случае измените его на 1.0и перекомпилируйте.


template<typename T_variant, typename T>
static inline auto

Потеряйте static inline. Шаблоны фактически встроены по определению, и вы не хотите, чтобы этот шаблон был статическим - вы не хотите, чтобы каждая единица перевода сохраняла свою собственную уникальную копию (в случае, если она не оптимизирована встроенным вкладышем).

Я не фанат Giraffe_case. Имена параметров шаблона должны быть короткими и CamelCase; вот рекомендую V.

У вашей std::visitлямбды есть бесполезный return arg;. На самом деле вся эта функция должна выглядеть как

template<class V, class T>
auto get_from_variant(V input) {
    return std::visit([&](auto&& arg) {
        return static_cast<T>(arg);
    }, input);
}

После удаления этого мусора у нас есть клетки мозга, которые могут сосредоточиться на следующем уровне педантизма: вы берете arg, пересылая ссылку ( auto&&), но на самом деле вы не пересылаете ее в static_cast. Может быть, нам следует использовать static_cast<T>(static_cast<decltype(arg)>(arg))здесь, чтобы если argэто ссылка rvalue, она переместилась в Tконструктор?

Но ждать; argне будет никогда быть Rvalue ссылки, потому что находится именующее выражение input! Так что, возможно, нам не следует ожидать изменения того, что argмы посещаем - мы могли бы принять это как const auto& arg. Но если мы не ожидаем изменений input, может быть, это следует принять… да, подождите, зачем мы делаем копию inputздесь? Просто возьмите его по константной ссылке для начала!

template<class V, class T>
auto get_from_variant(const V& input) {
    return std::visit([](const auto& arg) {
        return static_cast<T>(arg);
    }, input);
}

Я убрал [&]из лямбды, так как он не требует никаких захватов.

Мы также должны посмотреть на параметры шаблона get_from_variant. Vможно вывести и Tнельзя; всегда всегда имеет смысл ставить невыводимые параметры на первое место.

template<class T, class V>
auto get_from_variant(const V& input) {
    return std::visit([](const auto& arg) {
        return static_cast<T>(arg);
    }, input);
}

Теперь наш главный водитель выглядит как это :

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;

Между тем, у recursive_transformвас есть опечатка: const T inputкогда вы имели в виду const T& input. Эти опечатки можно найти механически, и вы должны это сделать!

  • Опять удалите static inlineиз шаблонов.

  • Имя _Fnзарезервировано для реализации; просто используйте F.

  • Копирование funcв лямбду не требуется; вы должны использовать [&]по умолчанию для каждой лямбды, которую вы пишете (если, как указано выше, вы можете обойтись простым []).

  • Честно говоря, если вы не страстно желаете следовать идиомам STL, просто передайте обратный вызов Fпо ссылке const и никогда не копируйте его. В C ++ есть место для изменяемых обратных вызовов с отслеживанием состояния, но transformне это место.

  • Ваш базовый случай сложнее, чем должен быть. Давай исправим это.

Собираем все вместе:

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;
}

И затем мне действительно кажется, что использование std::transformздесь является излишним: он читает input дважды , один раз для копирования и еще раз для преобразования. Предположим, мы просто сделали открытый код, вот так?

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;
}

Конечно, мы могли бы использовать диапазоны C ++ 20, чтобы сделать что-то вроде этого :

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());
}

Это медленнее компилируется и генерирует более крупный код, но он действительно может быть быстрее во время выполнения, если T::value_typeкопировать дорого, потому что мы исключаем копирование T::value_type- мы просто создаем прямо на месте.