J’ai bien conscience que c’est un peu avancé comme interrogation et que l’habitude des choses voudrait que j’aille la poser sur un forum anglais, mais d’une part : je ne sais pas lequel ; d’autre part : j’aimerais bien qu’on stimule un peu plus la communauté C++ de zeste de savoir et francophone en général
Mon cas d’usage
J’ai une interface par fonction membre virtuelle que je redéfinis pour un cas concret (si ça vous intéresse, ça vient d’ici : https://gitlab.com/inendi/inspector/ ).
Pour tous les paramètres inconnus de la classe abstraite, il y a un std::vector<std::any>>
.
using Params = std::vector<std::any>;
QWidget* PVDisplays::PVDisplayViewGroupBy::create_widget(Inendi::PVView* view,
QWidget* parent,
Params const& params) const
{
auto col1 = std::any_cast<PVCol>(params.at(0));
auto col2 = std::any_cast<PVCol>(params.at(1));
auto operation = std::any_cast<Operation>(params.at(2));
//...
}
Ce code me paraît redondant, sujet aux erreurs (notamment d’indice) et peu agréable à lire (il y a pire me direz-vous).
many_cast
J’ai donc essayé de simplifier l’usage avec un many_cast
calqué sur std::any_cast
.
J’ai essayé de le faire en une seule fonction, mais je n’y suis pas arrivé ; si vous avez une meilleure solution, je suis preneur !
template<class... Args, std::size_t... I>
auto mcimpl(auto& params, std::index_sequence<I...>)
{
return std::make_tuple(std::any_cast<Args>(params.at(I))...);
}
template<class... Args, class Seq = std::make_index_sequence<sizeof...(Args)>>
auto many_cast(auto& params)
{
return mcimpl<Args...>(params, Seq{});
}
Ce qui donne à l’usage :
using Params = std::vector<std::any>;
QWidget* PVDisplays::PVDisplayViewGroupBy::create_widget(Inendi::PVView* view,
QWidget* parent,
Params const& params) const
{
auto [col1, col2, operation] = many_cast<PVCol, PVCol, Operation>(params);
//...
}
Côté usage, ça me semble bien mieux : plus d’erreur possible dans les indices, et un one-liner.
Si d’aventure j’avais un nombre de paramètres dynamique, ils restent accessibles, ce n’est donc pas intrusif.
J’ai utilisé un std::tuple
, mais comme on le voit dans l’utilisation, ça reste un détail d’implémentation. Si d’autres alternatives vous viennent, je suis intéressé.
Les exceptions
Là où ça se corse, c’est si je cherche à gérer les exceptions. J’hésite à faire une gestion différente entre many_cast
et std::any_cast
. Les deux types d’exceptions potentielles à gérer sont std::bad_any_cast
et std::out_of_range
. Il y a bien évidemment toutes les exceptions des constructeurs de copie, mais il me semble que cela n’a pas d’impact particulier (et je ne vois pas l’intérêt de les gérer).
Copie uniquement
Autre difficulté (qui n’en est pas une dans mon cas d’usage, mais pourrait l’être si je réutilise many_cast
dans un autre contexte), many_cast
fait systématiquement une copie des paramètres pour les extraire.
std::any_cast
est plus subtil, il permet de récupérer par référence, ou par déplacement.
Je ne suis du tout confiant dans la robustesse de many_cast
pour ce type d’accès, qui plus est potentiellement mixé : est-ce que le passage via std::make_tuple
, qui est un détail d’implémentation, ne rend pas impossible de récupérer des références ?
Code générique
Pour jouer avec et préparer une généralisation :
#include <any>
#include <vector>
#include <tuple>
#include <string>
template<class... Args, std::size_t... I>
auto mcimpl(auto& params, std::index_sequence<I...>)
{
return std::make_tuple(std::any_cast<Args>(params.at(I))...);
}
template<class... Args, class Seq = std::make_index_sequence<sizeof...(Args)>>
auto many_cast(auto& params)
{
return mcimpl<Args...>(params, Seq{});
}
int main()
{
std::vector<std::any> params{42, 3.14, std::string{"bar"}};
auto [answer, pi, name] = many_cast<int, double, std::string>(params);
}
Hâte de lire vos réflexions