Considérez le morceau de code suivant qui utilise boost :: variant
(mais qui devrait également s'appliquer parfaitement à std :: variant
).
#include <vector> #include <boost/variant.hpp> int main() { boost::variant<std::vector<int>, std::vector<double> > vr = std::vector<int>(5, 5);; // OK, no problem. boost::apply_visitor([](auto a) { std::cout << a[0] << "\n"; }, vr); // ERROR: return types must not differ. //boost::apply_visitor([](auto a) { return a.begin(); }, vr); }
Ici, nous avons une variante qui consomme des vecteurs standard de différents types (par exemple, int
et double
dans cet exemple), et nous aimerions avoir un visiteur qui renvoie des objets de différents types (dans ce cas, des itérateurs au début du conteneur sous-jacent). Cependant, cela ne se compilera pas car évidemment std :: vector
n'est pas la même chose que std :: vector
. Existe-t-il un moyen efficace d'y parvenir, éventuellement grâce à une couche supplémentaire d'indirection?
4 Réponses :
Je pense que vous devez faire les choses différemment, les itérateurs sont différents dans votre cas actuel mais vous pouvez les contourner:
std::vector<boost::variant<int, double>> vr = std::vector<int>(5, 5);
C'est fondamentalement la même chose.
p>
Ce n'est vraiment pas la même chose.
Vous pouvez renvoyer une variante différente
template<typename Func, typename Variant> struct visitor_result; template<typename Func, typename ... Ts> struct visitor_result<Func, boost::variant<Ts...>> { using type = boost::variant<decltype(std::declval<Func>()(std::declval<Ts>()))...>; }; template<typename Func, typename Variant> using visitor_result_t = typename visitor_result<Func, Variant>::type; template<typename Func, typename Variant> visitor_result_t<Func, Variant> generic_visit(Func func, Variant variant) { return boost::apply_visitor([&](auto a) -> visitor_result_t<Func, Variant> { return func(a); }, variant); }
Étant donné un lambda générique et une variante, vous pouvez obtenir un type de retour approprié.
#include <iostream> #include <vector> #include <boost/variant.hpp> int main() { boost::variant<std::vector<int>, std::vector<double> > vr = std::vector<int>(5, 5); using iter_variant = boost::variant<std::vector<int>::iterator, std::vector<double>::iterator >; using value_variant = boost::variant<int, double>; // OK, no problem. boost::apply_visitor([](auto a) { std::cout << a[0] << "\n"; }, vr); // Also OK boost::apply_visitor([](auto a) -> iter_variant { return a.begin(); }, vr); // Also OK boost::apply_visitor([](auto a) -> value_variant { return a[0]; }, vr); }
Nécessite-t-il uniquement C ++ 17 ou bientôt C ++ 20?
@ RadosławCybulski avec boost :: variant
il fonctionne avec C ++ 14, et n'a besoin de C ++ 17 que pour std :: variant
Très gentil, bravo à vous, monsieur.
Utilisez également le type de résultat variant. En d'autres termes:
boost::variant<std::vector<int>, std::vector<double> > vr = std::vector<int>(5, 5);; boost::apply_visitor([](auto a) -> boost::variant<int, double> { using T = std::decay_t<decltype(a)>; if constexpr (std::is_same_v<T, std::vector<int>>) { int v = 0; for(auto q : a) v += q; return v; } else if constexpr (std::is_same_v<T, std::vector<double>>) { double v = 0; for(auto q : a) v += q; return v; } }, vr);
Je peux imaginer faire cela avec la déduction automatique du type de retour de variante correct, mais cela prendrait beaucoup de codage.
vous n'avez pas besoin de if constexpr
ici
Que voulez-vous dire?
vous pouvez écrire du code générique lorsque vous utilisez auto
. il vous suffit de trouver le type correct pour v
, qui est decltype (a) :: value_type
dans votre cas
En s'appuyant sur la réponse de @ Caleth, cela permet d'utiliser n'importe quelle variante sans dupliquer la liste des paramètres.
#include <vector> #include <variant> #include <type_traits> //Replace with more appropriate name template<typename Variant, typename Lambda> struct X_impl; template<typename...Ts, typename Lambda> struct X_impl<std::variant<Ts...>, Lambda>{ using type = std::variant<std::invoke_result_t<Lambda,Ts>...>; }; template<typename...Ts, typename Lambda> struct X_impl<const std::variant<Ts...>, Lambda>{ using type = std::variant<std::invoke_result_t<Lambda,const Ts>...>; }; template<typename Variant, typename Lambda> using X = typename X_impl<std::remove_reference_t<Variant>, Lambda>::type; template<typename Variant, typename Lambda> auto visit(Variant&& variant, Lambda&& lambda){ auto wrapped_lambda = [&lambda](auto&& arg) -> X<Variant,Lambda>{ using T = decltype(arg); return std::forward<Lambda>(lambda)(std::forward<T>(arg)); }; return std::visit(wrapped_lambda, std::forward<Variant>(variant)); } int main() { std::variant<std::vector<int>,const std::vector<double> > vr = std::vector<int>(5, 5); const std::variant<std::vector<int>,const std::vector<double> > c_vr = std::vector<int>(5, 5); auto& ref_vr = vr; auto& ref_c_vr = c_vr; auto visit_fnc = [](auto&& a){return a.begin();}; visit(vr, visit_fnc); visit(c_vr, visit_fnc); visit(ref_vr, visit_fnc); visit(ref_c_vr, visit_fnc); }
EDIT: Oh, on dirait que @Caleth a également ajouté une solution générale entre-temps.
En effet, ma réponse le fait, mais vous avez pris la peine de transmettre l'argument etc.
@Caleth Oui, je ne sais pas si j'ai tout attrapé. Je ne me suis pas soucié des trucs volatils
.
Que voulez-vous faire du résultat?