Question Comment créer des chaînes statiques à partir de types au moment de la compilation


J'ai un tas de types qui ont un nom. (Ils ont plus de fonctionnalités, mais pour cette discussion, seul le nom est pertinent.) Ces types et leurs noms sont configurés à la compilation en utilisant une macro:

#define DEFINE_FOO(Foo_)                        \
    struct Foo_ : public foo_base<Foo_> {       \
      static char const* name() {return #Foo_;} \
    }

Les types sont ensuite combinés dans des listes à la compilation (listes de compilation classiques récursives simples), à partir desquelles je dois créer le nom de la liste en concaténant les noms de ses objets:

template<class Foo, class Tail = nil>
struct foo_list {
  static std::string name_list() {return Foo::name() + "-" + Tail::name();}
};
template<class Foo>
struct foo_list<Foo,nil> {
  static std::string name_list() {return Foo::name();}
};

Le code se résume ici au point où il pourrait contenir des erreurs, mais en pratique, cela fonctionne plutôt bien.

Sauf que cela crée et copie des chaînes plutôt longues à l'exécution, qui représentent des types qui sont bien connus à la compilation. Puisqu'il s'agit d'un morceau de code plutôt sensible aux performances qui s'exécute sur des périphériques embarqués, j'aimerais changer cela pour que

  1. la chaîne de la liste est idéalement créée au moment de la compilation ou, s'il n'y a aucun moyen de le faire, une fois à l'exécution, et
  2. Je n'ai qu'à copier autour d'un pointeur vers une chaîne C, car, selon # 1, les chaînes sont fixes en mémoire.
  3. Cela compile avec C ++ 03, avec lequel nous sommes coincés maintenant.

Comment puis-je faire ceci?

(Au cas où cela élargirait l’arsenal de sales trucs utilisables pour cela: les noms des foo les objets ne sont créés et lus que par le code, et seuls les foo_list les chaînes de noms doivent être lisibles par l'homme.)


10
2017-10-23 07:20


origine


Réponses:


Vous voulez probablement regarder le coup de pouce mpl::string. Exemple à suivre une fois mon café lancé ...

EDIT: Alors le café a commencé ... :)

#include <iostream>

#include <boost/mpl/bool.hpp>
#include <boost/mpl/if.hpp>
#include <boost/mpl/string.hpp>
#include <boost/mpl/vector.hpp>

namespace mpl = boost::mpl;

struct foo
{
  typedef mpl::string<'foo'> name;
};

struct bar
{
  typedef mpl::string<'bar'> name;
};

struct gah
{
  typedef mpl::string<'gah'> name;
};

namespace aux
{

template <typename string_type, typename It, typename End>
struct name_concat
{
  typedef typename mpl::insert_range<string_type, typename mpl::end<string_type>::type, typename mpl::deref<It>::type::name>::type base;
  typedef typename aux::name_concat<base, typename mpl::next<It>::type, End>::name name;
};

template <typename string_type, typename End>
struct name_concat<string_type, End, End>
{
  typedef string_type name;
};

}

template <typename ...Types>
struct type_list
{
  typedef mpl::string<> base;
  typedef mpl::vector<Types...> type_seq;
  typedef typename aux::name_concat<base, typename mpl::begin<type_seq>::type, typename mpl::end<type_seq>::type>::name name;
};

int main(void)
{
  typedef typename type_list<foo, bar, gah>::name tlist_name;
  std::cout << mpl::c_str<tlist_name>::value << std::endl;
}

Je suis sûr que vous êtes plus que compétent pour modifier ce qui précède pour votre situation. NOTE: vous devrez ignorer les avertissements constants multi-caractères ...

Quelques mises en garde supplémentaires: la constante multi-caractères passée à mpl::string ne peut pas comporter plus de 4 caractères, donc, comment il doit être fragmenté (ou construit avec des caractères individuels), donc une longue chaîne peut être, mpl::string<'this', ' is ', 'a lo', 'ng s', 'trin', 'g'> Si cela ne peut pas être fait, alors ce qui précède ne fonctionnera pas.: /


4
2017-10-23 07:26



Je suis venu avec la solution suivante:

Le type est généré comme suit:

const char foo_str [] = "foo";
struct X
{
    static const char *name() { return foo_str; }
    enum{ name_size = sizeof(foo_str) };
};

Point-clé est que nous connaissons la longueur de son nom au moment de la compilation. Cela nous permet de calculer la longueur totale des noms en typelist:

template<typename list>
struct sum_size
{
    enum
    {
       value = list::head::name_size - 1 +
               sum_size<typename list::tail>::value
    };
};
template<>
struct sum_size<nil>
{
    enum { value = 0 };
};

Connaissant la longueur totale au moment de la compilation, nous pouvons allouer un tampon statique de taille appropriée pour la concaténation des chaînes - il n'y aura donc aucune allocation dynamique:

static char result[sum_size<list>::value + 1];

Ce tampon devrait être rempli à l'exécution, mais une seule fois, et cette opération est relativement peu coûteuse (beaucoup plus rapide que la solution précédente avec l'allocation dynamique de chaînes et leur concaténation en récursivité):

template<typename list>
const char *concate_names()
{
    static char result[sum_size<list>::value + 1];
    static bool calculated = false;
    if(!calculated)
    {
        fill_string<list>::call(result);
        calculated = true;
    }
    return result;
}

Voici le code complet:

Démo en direct sur Coliru

#include <algorithm>
#include <iostream>
using namespace std;

/****************************************************/

#define TYPE(X) \
const char X ## _str [] = #X; \
struct X \
{ \
    static const char *name() { return X ## _str; }  \
    enum{ name_size = sizeof(X ## _str) }; \
}; \
/**/

/****************************************************/

struct nil {};

template<typename Head, typename Tail = nil>
struct List
{
    typedef Head head;
    typedef Tail tail;
};

/****************************************************/

template<typename list>
struct sum_size
{
    enum { value = list::head::name_size - 1 + sum_size<typename list::tail>::value };
};
template<>
struct sum_size<nil>
{
    enum { value = 0 };
};

/****************************************************/

template<typename list>
struct fill_string
{
    static void call(char *out)
    {
        typedef typename list::head current;
        const char *first = current::name();
        fill_string<typename list::tail>::call
        (
            copy(first, first + current::name_size - 1, out)
        );
    }
};

template<>
struct fill_string<nil>
{
    static void call(char *out)
    {
        *out = 0;
    }
};

/****************************************************/

template<typename list>
const char *concate_names()
{
    static char result[sum_size<list>::value + 1];
    static bool calculated = false;
    if(!calculated)
    {
        fill_string<list>::call(result);
        calculated = true;
    }
    return result;
}

/****************************************************/

TYPE(foo)
TYPE(bar)
TYPE(baz)

typedef List<foo, List<bar, List<baz> > > foo_list;

int main()
{
    cout << concate_names<foo_list>() << endl; 
}

Le résultat est:

foobarbaz

P.S. Comment utilisez-vous une chaîne concaténée? Nous n'avons peut-être pas besoin de générer de chaîne concaténée, ce qui réduit les besoins en espace de données.

Par exemple, si vous avez juste besoin d'imprimer une chaîne, alors

template<typename list>
void print();

sera suffisant. Mais l'inconvénient est que, tout en réduisant la taille des données, cela pourrait conduire à une augmentation de la taille du code.


4
2017-10-30 23:34



  1. Vous pourriez faire la chaîne static et vous devez seulement construire la chaîne une fois à l'exécution et seulement quand c'est nécessaire.
  2. Ensuite, vous retournez une référence const pour qu'il n'y ait pas de copie inutile.

Exemple:

template<class Foo, class Tail = nil>
struct foo_list {
  static const std::string& name_list() {
     static std::string names = Foo::name() + std::string("-") + Tail::name();
     return names;
  }
};

template<class Foo>
struct foo_list<Foo,nil> {
  static const std::string& name_list() {
     static std::string names = Foo::name();
     return names;
  }
};

Peut-être ne pas être le code exact que vous écrirez, mais je pense que cela vous donne le point. En outre, vous pouvez retourner un const char* en faisant names.c_str().


3
2017-10-23 07:34



Vous pouvez envisager d'utiliser une étape de génération externe au lieu d'une solution dans le langage. Par exemple, vous pouvez écrire un outil basé sur Clang pour analyser les fichiers pertinents et créer le fichier. T::name implémentations automatiques dans une autre TU. Ensuite, intégrez-le dans votre script de construction.


2
2017-10-30 22:49



Si nous pouvions supposer que votre seule exigence est de diffuser les noms des classes - ce qui signifie que vous n’avez pas besoin des chaînes concaténées à l’ensemble - vous pouvez simplement reporter le streaming tout en bénéficiant de la méta-programmation (comme Evgeny le fait déjà). souligné).

Bien que cette solution ne corresponde pas à votre exigence n ° 1 (une chaîne concaténée), je souhaiterais quand même signaler une solution pour les autres lecteurs.

Au lieu de parcourir une liste de types à la compilation, l’idée est de créer une séquence d’adresses T::name() fonctions et passer dans une fonction de streaming en cas de besoin. Cela est possible car les variables avec liaison externe peuvent être utilisées comme arguments de type non modèle. Votre kilométrage peut varier en termes de données et de taille de code, mais à moins que vous ne soyez dans un environnement très performant, je m'attends à ce que cette approche soit au moins aussi appropriée car aucune chaîne supplémentaire ne doit être créée à l'exécution.

Notez que j'ai délibérément utilisé des modèles variadiques (non disponibles en C ++ 03) car ils sont beaucoup plus lisibles et simples à raisonner.

"Violon" disponible ici.

#include <ostream>
#include <boost/core/ref.hpp>
#include <boost/bind.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/for_each.hpp>

namespace mpl = boost::mpl;


template<typename>
class foo_base
{};

#define DECLARE_FOO(Foo_) \
    struct Foo_ : public foo_base<Foo_> { \
        static char const* name() {return #Foo_;} \
    };


// our own integral constant because mpl::integral_c would have to be specialized anyway
template<typename T, T Value>
struct simple_integral_c
{
    operator T() const { return Value; }
};

template<typename T, T ...Values>
struct ic_tuple : mpl::vector<simple_integral_c<T, Values>...>
{};


typedef const char*(*NameFunction)();

template <NameFunction ...Functions>
struct function_list : ic_tuple<NameFunction, Functions...>
{};

template <typename ...Types>
struct function_of_list : function_list<&Types::name...>
{};


struct print_type
{
    void operator ()(std::ostream& os, NameFunction name)
    {
        if (nth++)
            os << "-";
        os << name();
    }

    print_type(): nth(0) {}

private:
    int nth;
};

// streaming function
template<NameFunction ...Functions>
std::ostream& operator <<(std::ostream& os, function_list<Functions...>)
{
    mpl::for_each<function_list<Functions...>>(
        boost::bind<void>(print_type(), boost::ref(os), _1)
    );

    return os;
}

Ces jours-ci avec C ++ 14 on écrirait probablement la solution avec une bibliothèque puissante comme hana, voyez ceci violon hana.


0
2018-01-04 21:08