local_buffer<T,N> – exemple

Je vous propose trois versions, en ordre chronologique :

Version C++ 11

J'ai utilisé <type_traits> et std::conditional de C++ 11.

#include <vector>
#include <array>
#include <cstdlib>
#include <cassert>
#include <type_traits>

La classe local_buffer<T,N,STATIC_N_BOUNDARY> et sa fonction create() opèrent par valeur, ce qui est efficace même avec un conteneur de grande taille grâce à l'optimisation nommée Copy Elision et, dans le pire des cas, à la sémantique de mouvement.

//
// Note: efficace avec C++ 11 grâce à la sémantique de mouvement
//
template <class T, std::size_t N, std::size_t STATIC_N_BOUNDARY = 1024 * 4>
   struct local_buffer {
   private:
      enum { STATIC_ALLOC = (sizeof(T) * N <= STATIC_N_BOUNDARY) };
   public:
      using implementation_type = std::conditional_t<
         STATIC_ALLOC, std::array<T, N>, std::vector<T>
      >;
      using efficient_sequence_constructed_implementation_type = std::vector<T>;
   private:
      class array_type {};
      class vector_type {};
      using selector_type =  std::conditional_t<
         STATIC_ALLOC, array_type, vector_type
      >;
      static implementation_type create(array_type) {
         return implementation_type{};
      }
      static implementation_type create(vector_type) {
         return implementation_type(N,T{});
      }
      template <class It>
         static implementation_type create(vector_type, It debut, It fin) {
            assert(distance(debut, fin) <= N);
            return implementation_type(debut, fin);
         }
   public:
      static implementation_type create() {
         return create(selector_type());
      }
      template <class It>
         static efficient_sequence_constructed_implementation_type create(It debut, It fin) {
            assert(distance(debut, fin) <= N);
            return efficient_sequence_constructed_implementation_type(debut, fin);
         }
   };
template <class T, std::size_t N>
   auto create_local_buffer() -> decltype(local_buffer<T, N>::create()) {
      return local_buffer<T, N>::create();
   }

Un petit programme de test suit, pour montrer que le tout fonctionne bel et bien.

#include <algorithm>
int main() {
   using namespace std;
   auto lbi = create_local_buffer<int,1000>(); // lbi --> array<int,1000>
   auto lbd = create_local_buffer<double, 1000>(); // lbd --> vector<double>
   fill(begin(lbi), end(lbi), -1);
   fill(begin(lbd), end(lbd), 3.14159);
}

Version C++ 17

Cette solution, comme la précédente, ne repose que sur des outils standards.

#include <vector>
#include <array>

Ce mécanisme très chouette qu'est if constexpr permet de remplacer quelques dizaines de lignes d'effort cérébral par une fonction courte, simple et efficace.

template <class T, std::size_t N, std::size_t STATIC_N_BOUNDARY = 1024 * 4>
   auto create_local_buffer() {
      if constexpr(sizeof(T) * N <= STATIC_N_BOUNDARY)
         return std::array<T,N>{};
      else
         return std::vector<T>(N);
   }

Un petit programme de test suit, pour montrer que le tout fonctionne bel et bien.

#include <algorithm>
int main() {
   using namespace std;
   auto lbi = local_buffer<int,1000>::create(); // lbi --> array<int,1000>
   auto lbd = local_buffer<double, 1000>::create(); // lbd --> vector<double>
   fill(begin(lbi), end(lbi), -1);
   fill(begin(lbd), end(lbd), 3.14159);
}

Version C++ 17 avec CTAD

Encore une fois, nous nous limiterons à des outils standards. Ce que nous essaierons toutefois de faire est alléger la tâche d'écriture du code client.

Vous avez peut-être remarqué que, bien que nous connaissions à la compilation la taille du tampon à créer et le type des éléments à y placer, nous faisons face à deux syntaxes, soit array<T,N>{} pour créer un tableau sans allouer de mémoire dynamiquement (pour un array, il faut que N soit connu à la compilation), et vector<T>(N) pour créer un vecteur et allouer dynamiquement la mémoire pour entreposer des éléments (pour un vector, N peut être connu à l'exécution).

#include <vector>
#include <array>

Pour uniformiser la syntaxe dans le cas particulier où N est connu à la compilation, ajoutons une classe dynamic_array<T,N> qui dérivera de vector<T>, et dont le constructeur par défaut appellera le constructeur de vector<T> pour N éléments.

Notez l'ajout de using std::vector<T>::vector; pour exposer les constructeurs du parent qui n'auront pas été masqués par l'enfant (donc tous ceux qui ne sont pas le constructeur par défaut).

template <class T, std::size_t N>
   struct dynamic_array : std::vector<T> {
      dynamic_array() : std::vector<T>(N) {
      }
      using std::vector<T>::vector;
   };

Nous sommes donc maintenant capables d'ajuster la fonction create_local_buffer<T,N>() pour qu'elle utilise un dynamic_array<T,N>{} au lieu d'un std::vector<T>(N).

template <class T, std::size_t N, std::size_t STATIC_N_BOUNDARY = 1024 * 4>
   auto create_local_buffer() {
      if constexpr(sizeof(T) * N <= STATIC_N_BOUNDARY)
         return std::array<T,N>{};
      else
         return dynamic_array<T,N>{};
   }

La « magie » de CTAD se manifestera grâce à l'alias local_buffer<T,N> qui correspondra au type que retournerait la fonction create_local_buffer<T,N>() si elle était appelée.

J'ai choisi de maintenir la fonction create_local_buffer<T,N>(), car il se peut que certain(e)s préfèrent cette écriture, mais elle n'est pas strictement nécessaire, et nous aurions pu l'éliminer puis simplement écrire l'alias local_buffer<T,N> sous la forme suivante :

#include <type_traits>
template <class T, std::size_t N>
   using local_buffer = std::conditional_t<
      (sizeof(T) * N <= 1024 * 4), std::array<T,N>, dynamic_array<T,N>
   >;
template <class T, std::size_t N>
   using local_buffer = decltype(create_local_buffer<T,N>());

Ceci permet d'écrire le code utilisant local_buffer<T,N> comme s'il s'agissait d'un type tout ce qu'il y a de plus normal.

Un petit programme de test suit, pour montrer que le tout fonctionne bel et bien.

#include <algorithm>
int main() {
   using namespace std;
   local_buffer<int,1000> lbi; // lbi --> array<int,1000>
   local_buffer<double, 1000> lbd; // lbd --> vector<double>
   fill(begin(lbi), end(lbi), -1);
   fill(begin(lbd), end(lbd), 3.14159);
}

Valid XHTML 1.0 Transitional

CSS Valide !