J'ai une structure qui, dans une représentation extrêmement simplifiée, ressemble à ceci:
struct Flags { const std::array<unsigned int, 8> flags; Flags(std::vector<unsigned int> initialFlagValues) : flags(initialFlagValues) {} };
Qui bien sûr ne compile pas. Pour les besoins de mon programme, j'aimerais initialiser un nombre arbitraire d'éléments dans flags
, en fonction de la longueur d'un paramètre ( std :: vector
, C- style array, ou autre) passé dans le constructeur de la structure.
Maintenant, j'aimerais utiliser un std :: array
à l'intérieur de la structure, car la structure elle-même est créée plusieurs fois (donc un std :: vector
ne serait pas idéal ici en raison de nombreuses allocations / désallocations), mais le nombre de valeurs dans flags
qui doivent être initialisées n'est pas toujours le même.
Est-il possible d'initialiser un nombre spécifique de champs dans flags
en fonction de la taille du conteneur de séquence passé comme paramètre dans le constructeur? p>
4 Réponses :
std :: array
est un agrégat. Cela signifie que la seule façon de l'initialiser est avec une braced_init_list ( {}
). Il n'y a aucun moyen de convertir un std :: vector
en une braced_init_list donc une chose que vous pouvez faire est d'utiliser une boucle à l'intérieur du constructeur comme
Flags(std::vector<unsigned int> initialFlagValues) : flags{} // zero out flags { auto size = std::min(initialFlagValues.size(), flags.size()) for (size_t i = 0; i < size; ++i) flags[i] = initialFlagValues[i]; }
Utilisez une fonction d'assistance, peut-être sous la forme d'un lambda:
Flags(std::vector<unsigned int> initialFlagValues) : flags([](const auto& init) { std::array<unsigned int, 8> flags; // bounds check omitted for brevity std::copy(init.begin(), init.end(), flags.begin()); return flags; }(initialFlagValues)) {}
Vous pouvez également tout faire au (presque) moment de la compilation, sans itération:
#include <iostream> #include <type_traits> #include <iterator> struct Omg { static constexpr std::size_t SIZE = 8; template<class Container> Omg(Container &&c) : Omg(std::forward<Container>(c), std::make_index_sequence<SIZE>{}) {} void omg() const { for(auto i: array) std::cout << i << ' '; std::cout << '\n'; } private: template<class C, std::size_t... is> Omg(C &&c, std::index_sequence<is...>) : array{get<is>(std::forward<C>(c))...} {} template<std::size_t i, class C> static constexpr auto get(C &&c) { return i < std::size(c)? c[i] : 0; } std::array<int, SIZE> array; }; int main() { Omg(std::vector<int>{0, 1, 2, 3}).omg(); int nyan[] = {42, 28, 14}; Omg(nyan).omg(); }
Peut-être que la fonction d'assistance peut être un constructeur délégué
struct Flags { std::array<unsigned int, 8u> const flagsArr; template <typename T, std::size_t ... Is> Flags (T const & iFV, std::index_sequence<Is...>) : flagsArr{ Is < std::size(iFV) ? iFV[Is] : 0u ... } {} template <typename T> Flags (T const & iFV) : Flags{iFV, std::make_index_sequence<8u>{}} {} };
Vous pouvez généraliser avec pour les types génériques prenant en charge les opérateurs []
et std :: size ( )
(ainsi que les tableaux de style C) comme suit
struct Flags { std::array<unsigned int, 8u> const flagsArr; template <std::size_t ... Is> Flags (std::vector<unsigned int> iFV, std::index_sequence<Is...>) : flagsArr{ Is < iFV.size() ? iFV[Is] : 0u ... } {} Flags (std::vector<unsigned int> iFV) : Flags{iFV, std::make_index_sequence<8u>{}} {} };
Que se passe-t-il si
initialFlagValues
est trop grand? Qu'arrivera-t-il aux éléments restants siinitialFlagValues
est inférieur à8
, sont-ils censés être mis à zéro ou laissés indéterminés? Voulez-vous une initialisation spécifique ou une affectation serait-elle également correcte? La dernière question n'a pas d'importance si le type d'élément estunsigned int
, mais peut avoir de l'importance pour les types qui ne sont pas assignables.Notez que faire une donnée membre
const
est une décision qui vient avec beaucoup de répercussions (pas d'affectation, par exemple). Vous ne devriez le faire que lorsque le besoin est démontré.