Функция get_from_variant в C ++
Это следующий вопрос для функции 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хорош ли дизайн функции ? Хорошая идея или использование или нет? Любые комментарии приветствуются.
Ответы
Я не слежу за этой цепочкой с самого начала, поэтому к этому моменту я смущен больше, чем вы ожидаете от читателей. Было бы неплохо, если бы вы каждый раз предоставляли полный компилируемый пример - даже просто как ссылку 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- мы просто создаем прямо на месте.