Estoy tratando de portar un código de Python a C++. El código de Python tiene una función foo que puede tomar listas anidadas de enteros, con una profundidad de lista variable. Por ejemplo, estas son llamadas de funciones legítimas a foo:
foo([ [], [[]], [ [], [[]] ] ]) foo([1]) foo([ [1], [2, 3, [4, 5]], [ [6], [7, [8, 9], 10] ] ])¿Cuáles deberían ser las firmas del método para un método C++ que puede aceptar este tipo de argumento?
Terminé resolviéndolo con esta estructura de datos:
using tIntList = std::vector<int>; using tListOrInt = std::variant<tIntList, int>; class VariableDepthList { public: using tListOfNestedLists = std::vector<VariableDepthList>; using tListElement = std::variant<tListOfNestedLists, tIntList, int>; explicit VariableDepthList(tListElement _element) : element(std::move(_element)) {}; tListElement element; };y luego varias sobrecargas de foo que toman cada uno de los tipos de variantes.
Aquí hay un ejemplo, construyendo la lista [ [], [[]], [ [], [[]] ] ] :
using tListOfNestedLists = VariableDepthList::tListOfNestedLists; VariableDepthList deep_list(tListOfNestedLists{ VariableDepthList(tIntList{}), // [] VariableDepthList(tListOfNestedLists{VariableDepthList(tIntList{})}), // [[]] VariableDepthList(tListOfNestedLists{ VariableDepthList(tIntList{}), // [] VariableDepthList(tListOfNestedLists{VariableDepthList(tIntList{})}) // [[]] }) });aquí está mi opinión
#include <iostream> #include <variant> #include <vector> // A class containing either a T or a nest<T> template<class T> struct nest { auto cbegin() const { return d.cbegin(); } auto cend() const { return d.cend(); } auto begin() const { return d.cbegin(); } auto end() const { return d.cend(); } auto begin() { return d.begin(); } auto end() { return d.end(); } std::vector<std::variant<T, nest<T>>> d; }; namespace detail { template<class... Ts> // helper type for visitor struct overloaded : Ts... { using Ts::operator()...; }; template<class... Ts> // deduction guide for helper type overloaded(Ts...) -> overloaded<Ts...>; template<class T> void foo_helper(const std::variant<T, nest<T>>& n) { std::visit(overloaded { [](const T& v) { std::cout << v; }, [](const nest<T>& v) { foo(v); } }, n); }; } // namespace detail template<class T> void foo(const nest<T>& list) { auto it = list.begin(); std::cout << '['; if(it != list.end()) { detail::foo_helper(*it); for(++it; it != list.end(); ++it) { std::cout << ','; detail::foo_helper(*it); } } std::cout << ']'; } int main() { std::cout << "[[1],[2,3,[4,5]],[[6],[7,[8,9],10]]] <- what we aim for\n"; foo(nest<int>{{nest<int>{{1}}, nest<int>{{2, 3, nest<int>{{4, 5}}}}, nest<int>{{nest<int>{{6}}, nest<int>{{7, nest<int>{{8,9}}, 10}}}}}} ); }Producción:
[[1],[2,3,[4,5]],[[6],[7,[8,9],10]]] <- what we aim for [[1],[2,3,[4,5]],[[6],[7,[8,9],10]]]Aquí hay una manera que es bastante simple de definir y usar:
#include <variant> #include <vector> struct VariableDepthList : std::variant<std::vector<VariableDepthList>, int> { private: using base = std::variant<std::vector<VariableDepthList>, int>; public: using base::base; VariableDepthList(std::initializer_list<VariableDepthList> v) : base(v) {} }; Esto se basa en el hecho de que su tipo es un int o una lista de (el mismo tipo), agregando un constructor initializer_list solo para facilitar su uso.
Es posible que desee agregar alguna función auxiliar como is_vector() / is_value() también.
Aquí hay un ejemplo usándolo:
#include <iostream> void foo(const VariableDepthList& v) { // Use like a variant. This is a print function if (auto* as_vector = std::get_if<std::vector<VariableDepthList>>(&v)) { if (as_vector->empty()) { std::cout << "[]"; return; } std::cout << "[ "; bool first = true; for (const auto& el : *as_vector) { if (!first) { std::cout << ", "; } first = false; foo(el); } std::cout << " ]"; } else { auto* as_int = std::get_if<int>(&v); std::cout << *as_int; } } int main() { foo({}); std::cout << '\n'; foo({ 1 }); std::cout << '\n'; foo({ {}, {{}}, { {}, {{}} } }); foo( {{1},{2,3,{4,5}},{{6},{7,{8,9},10}}} ); std::cout << '\n'; }