156 votes

Idéalement Déclarer au Moment de la Compilation des Chaînes en C++

Être capable de créer et de manipuler des chaînes de caractères lors de la compilation en C++ dispose de plusieurs applications utiles. Bien qu'il est possible de créer au moment de la compilation des chaînes en C++, le processus est très lourd, que la chaîne de caractères doit être déclaré comme un variadic séquence de caractères, par exemple:

using str = sequence<'H', 'e', 'l', 'l', 'o', ', ', 'w', 'o', 'r', 'l', 'd', '!'>;

Des opérations telles que la concaténation de chaîne, sous-chaîne d'extraction, et beaucoup d'autres, peuvent être facilement mises en œuvre que les opérations sur les séquences de caractères. Est-il possible de déclarer au moment de la compilation des chaînes plus facilement? Si non, est-il une proposition dans les œuvres qui permettrait à la pratique de la déclaration de la compilation des chaînes?

Pourquoi Les Approches Existantes De L'Échec

Idéalement, nous aimerions être en mesure de déclarer au moment de la compilation des chaînes comme suit:

// Approach 1
using str1 = sequence<"Hello, world!">;

ou, à l'aide définis par l'utilisateur littéraux,

// Approach 2
constexpr auto str2 = "Hello, world!"_s;

decltype(str2) aurait un constexpr constructeur. Une messier version de la méthode 1 est possible de mettre en œuvre, en tirant parti du fait que vous pouvez effectuer les opérations suivantes:

template <unsigned Size, const char Array[Size]>
struct foo;

Toutefois, le tableau aurait besoin d'avoir une liaison externe, afin d'obtenir de 1 à travailler, vous devez écrire quelque chose comme ceci:

/* Implementation of array to sequence goes here. */

constexpr const char str[] = "Hello, world!";

int main()
{
    using s = string<13, str>;
    return 0;
}

Inutile de dire que cela est très gênant. Approche 2 est en fait pas possible de mettre en œuvre. Si nous étions à déclarer (constexpr) littérale de l'opérateur, alors comment pourrions-nous préciser le type de retour? Depuis que nous avons besoin l'opérateur pour retourner un variadic séquence de caractères, de sorte que nous aurions besoin d'utiliser l' const char* paramètre pour spécifier le type de retour:

constexpr auto
operator"" _s(const char* s, size_t n) -> /* Some metafunction using `s` */

Il en résulte une erreur de compilation, car s n'est pas un constexpr. En essayant de contourner ce problème en procédant de la manière suivante n'aide pas beaucoup.

template <char... Ts>
constexpr sequence<Ts...> operator"" _s() { return {}; }

La norme veut que ce spécifique littérale de l'opérateur formulaire est réservé pour l'entier et les types à virgule flottante. Alors qu' 123_s , abc_s ne le serait pas. Si nous fossé définis par l'utilisateur littéraux tout à fait, et juste utiliser un constexpr fonction?

template <unsigned Size>
constexpr auto
string(const char (&array)[Size]) -> /* Some metafunction using `array` */

Comme avant, nous rencontrons le problème est que le tableau, maintenant un paramètre à l' constexpr fonction, est en lui-même n'est plus un constexpr type.

Je crois qu'il devrait être possible de définir un préprocesseur C macro qui prend une chaîne de caractères et la taille de la chaîne en argument, et renvoie une séquence comprenant les caractères dans la chaîne (à l'aide d' BOOST_PP_FOR, stringification, tableau des indices, etc). Cependant, je n'ai pas le temps (ou suffisamment d'intérêt) pour mettre en œuvre une telle macro =)

134voto

Howard Hinnant Points 59526

Je n'ai rien vu de match de l'élégance de Scott Schurr de l' str_const présentés au C++ Maintenant 2012. Il exige constexpr .

Voici comment vous pouvez l'utiliser, et ce qu'il peut faire:

int
main()
{
    constexpr str_const my_string = "Hello, world!";
    static_assert(my_string.size() == 13, "");
    static_assert(my_string[4] == 'o', "");
    constexpr str_const my_other_string = my_string;
    static_assert(my_string == my_other_string, "");
    constexpr str_const world(my_string, 7, 5);
    static_assert(world == "world", "");
//  constexpr char x = world[5]; // Does not compile because index is out of range!
}

Il n'est pas beaucoup plus frais qu'au moment de la compilation le contrôle de la portée!

À la fois l'utilisation et la mise en œuvre, est libre de macros. Et il n'y a pas de limite artificielle sur la taille de la chaîne. J'ai poster la mise en œuvre ici, mais je suis en respectant Scott implicite du droit d'auteur. La mise en œuvre est sur une seule diapositive de sa présentation lié ci-dessus.

47voto

user1115339 Points 71

Je crois qu'il devrait être possible de définir une macro du préprocesseur C prend une chaîne de caractères et la taille de la chaîne en argument, et renvoie un séquence constituée des caractères dans la chaîne (à l'aide de BOOST_PP_FOR, stringification, tableau des indices, etc). Cependant, je n'ai pas le temps (ou suffisamment d'intérêt) pour mettre en œuvre ces une macro

il est possible de mettre en œuvre ce sans compter sur un coup de pouce, en utilisant très simple macro et certains de C++11 caractéristiques:

  1. les lambdas variadic
  2. modèles
  3. généralisée des expressions constantes
  4. non-membre de données statiques initialiseurs
  5. l'initialisation uniforme

(les deux derniers ne sont pas strictement nécessaires ici)

  1. nous devons être en mesure d'instancier un variadic template avec de l'utilisateur fourni indicies de 0 à N - un outil aussi utile par exemple pour développer un tuple dans variadic template fonction de l'argument (voir questions: Comment puis-je développer un tuple dans variadic template de la fonction d'arguments?
    "déballage" un tuple pour appeler un correspondant pointeur de fonction)

    namespace  variadic_toolbox
    {
        template<unsigned  count, 
            template<unsigned...> class  meta_functor, unsigned...  indices>
        struct  apply_range
        {
            typedef  typename apply_range<count-1, meta_functor, count-1, indices...>::result  result;
        };
    
        template<template<unsigned...> class  meta_functor, unsigned...  indices>
        struct  apply_range<0, meta_functor, indices...>
        {
            typedef  typename meta_functor<indices...>::result  result;
        };
    }
    
  2. puis de définir une variadic template appelé chaîne avec des non-type de paramètre char:

    namespace  compile_time
    {
        template<char...  str>
        struct  string
        {
            static  constexpr  const char  chars[sizeof...(str)+1] = {str..., '\0'};
        };
    
        template<char...  str>
        constexpr  const char  string<str...>::chars[sizeof...(str)+1];
    }
    
  3. maintenant, la partie la plus intéressante - pour transmettre des chaînes de caractères dans la chaîne modèle:

    namespace  compile_time
    {
        template<typename  lambda_str_type>
        struct  string_builder
        {
            template<unsigned... indices>
            struct  produce
            {
                typedef  string<lambda_str_type{}.chars[indices]...>  result;
            };
        };
    }
    
    #define  CSTRING(string_literal)                                                        \
        []{                                                                                 \
            struct  constexpr_string_type { const char * chars = string_literal; };         \
            return  variadic_toolbox::apply_range<sizeof(string_literal)-1,                 \
                compile_time::string_builder<constexpr_string_type>::produce>::result{};    \
        }()
    

une simple concaténation de démonstration illustre l'utilisation:

    namespace  compile_time
    {
        template<char...  str0, char...  str1>
        string<str0..., str1...>  operator*(string<str0...>, string<str1...>)
        {
            return  {};
        }
    }

    int main()
    {
        auto  str0 = CSTRING("hello");
        auto  str1 = CSTRING(" world");

        std::cout << "runtime concat: " <<  str_hello.chars  << str_world.chars  << "\n <=> \n";
        std::cout << "compile concat: " <<  (str_hello * str_world).chars  <<  std::endl;
    }

http://liveworkspace.org/code/MyRbM$6

24voto

dyp Points 19641

Edit: comme Howard Hinnant (et moi un peu dans mon commentaire à l'OP) a souligné, vous ne pourriez pas besoin d'un type avec chaque caractère de la chaîne comme un seul argument de modèle. Si vous avez besoin de ce, il y a une macro-gratuit solution ci-dessous.

Il y a un truc que j'ai trouvé tout en essayant de travailler avec des chaînes au moment de la compilation. Il nécessite d'introduire un autre type d'ailleurs la "chaîne de modèle", mais à l'intérieur des fonctions, vous pouvez limiter la portée de ce type.

Ils n'utilisent pas de macros, mais plutôt un peu de C++11 caractéristiques.

#include <iostream>

// helper function
constexpr unsigned c_strlen( char const* str, unsigned count = 0 )
{
    return ('\0' == str[0]) ? count : c_strlen(str+1, count+1);
}

// helper "function" struct
template < char t_c, char... tt_c >
struct rec_print
{
    static void print()
    {
        std::cout << t_c;
        rec_print < tt_c... > :: print ();
    }
};
    template < char t_c >
    struct rec_print < t_c >
    {
        static void print() { std::cout << t_c; }
    };


// destination "template string" type
template < char... tt_c >
struct exploded_string
{
    static void print()
    {
        rec_print < tt_c... > :: print();
    }
};

// struct to explode a `char const*` to an `exploded_string` type
template < typename T_StrProvider, unsigned t_len, char... tt_c >
struct explode_impl
{
    using result =
        typename explode_impl < T_StrProvider, t_len-1,
                                T_StrProvider::str()[t_len-1],
                                tt_c... > :: result;
};

    template < typename T_StrProvider, char... tt_c >
    struct explode_impl < T_StrProvider, 0, tt_c... >
    {
         using result = exploded_string < tt_c... >;
    };

// syntactical sugar
template < typename T_StrProvider >
using explode =
    typename explode_impl < T_StrProvider,
                            c_strlen(T_StrProvider::str()) > :: result;


int main()
{
    // the trick is to introduce a type which provides the string, rather than
    // storing the string itself
    struct my_str_provider
    {
        constexpr static char const* str() { return "hello world"; }
    };

    auto my_str = explode < my_str_provider >{};    // as a variable
    using My_Str = explode < my_str_provider >;    // as a type

    str.print();
}

13voto

Yankes Points 456

Si vous ne voulez pas utiliser le Boost de solution , vous pouvez créer une macro qui va faire la même chose:

#define MACRO_GET_1(str, i) \
    (sizeof(str) > (i) ? str[(i)] : 0)

#define MACRO_GET_4(str, i) \
    MACRO_GET_1(str, i+0),  \
    MACRO_GET_1(str, i+1),  \
    MACRO_GET_1(str, i+2),  \
    MACRO_GET_1(str, i+3)

#define MACRO_GET_16(str, i) \
    MACRO_GET_4(str, i+0),   \
    MACRO_GET_4(str, i+4),   \
    MACRO_GET_4(str, i+8),   \
    MACRO_GET_4(str, i+12)

#define MACRO_GET_64(str, i) \
    MACRO_GET_16(str, i+0),  \
    MACRO_GET_16(str, i+16), \
    MACRO_GET_16(str, i+32), \
    MACRO_GET_16(str, i+48)

#define MACRO_GET_STR(str) MACRO_GET_64(str, 0), 0 //guard for longer strings

using seq = sequence<MACRO_GET_STR("Hello world!")>;

le seul problème est de taille fixe de 64 caractères (plus le zéro). Mais il peut être facilement modifiée en fonction de vos besoins.

6voto

Evgeny Panasyuk Points 5408

Je crois qu'il devrait être possible de définir un préprocesseur C macro qui prend une chaîne de caractères et la taille de la chaîne en argument, et renvoie une séquence comprenant les caractères dans la chaîne (à l'aide de BOOST_PP_FOR, stringification, tableau des indices, etc)

Il y a de l'article: Utilisant des chaînes de caractères en C++ template metaprograms par Abel Sinkovics et Dave Abrahams.

Il a une certaine amélioration par rapport à votre idée de l'utilisation de macro + BOOST_PP_REPEAT - il ne nécessite pas de passage explicite de la taille de la macro. En bref, il est basé sur fixe la limite supérieure pour la taille de la chaîne et de la "chaîne de dépassement de protection":

template <int N>
constexpr char at(char const(&s)[N], int i)
{
    return i >= N ? '\0' : s[i];
}

plus conditionnelle boost::mpl::push_back.


J'ai changé mes accepté de répondre à Yankes solution, car il permet de résoudre ce problème spécifique, et le fait avec élégance, sans l'utilisation de constexpr ou complexe de préprocesseur code.

Si vous acceptez les zéros à droite, écrite à la main macro en boucle, 2x repetion de la chaîne d'élargissement de la macro, et n'ont pas de Boost, alors je suis d'accord - c'est mieux. Cependant, avec le Boost, il serait juste trois lignes:

DÉMONSTRATION EN DIRECT

#include <boost/preprocessor/repetition/repeat.hpp>
#define GET_STR_AUX(_, i, str) (sizeof(str) > (i) ? str[(i)] : 0),
#define GET_STR(str) BOOST_PP_REPEAT(64,GET_STR_AUX,str) 0

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X