Dogodne deklarowanie ciągów czasu kompilacji w C ++

138

Możliwość tworzenia i manipulowania napisami podczas kompilacji w C ++ ma kilka przydatnych aplikacji. Chociaż możliwe jest tworzenie ciągów znaków czasu kompilacji w C ++, proces ten jest bardzo uciążliwy, ponieważ ciąg musi być zadeklarowany jako zmienna sekwencja znaków, np.

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

Operacje, takie jak konkatenacja ciągów, wyodrębnianie podciągów i wiele innych, można łatwo zaimplementować jako operacje na sekwencjach znaków. Czy można wygodniej deklarować ciągi czasu kompilacji? Jeśli nie, to czy w pracach jest propozycja, która pozwoliłaby na wygodne deklarowanie ciągów czasu kompilacji?

Dlaczego istniejące podejścia zawodzą

Idealnie chcielibyśmy móc zadeklarować ciągi czasu kompilacji w następujący sposób:

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

lub używając literałów zdefiniowanych przez użytkownika,

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

gdzie decltype(str2)miałby constexprkonstruktora. Bardziej chaotyczna wersja podejścia 1 jest możliwa do wdrożenia, wykorzystując fakt, że możesz wykonać następujące czynności:

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

Jednak tablica musiałaby mieć zewnętrzne połączenie, więc aby podejście 1 działało, musielibyśmy napisać coś takiego:

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

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

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

Nie trzeba dodawać, że jest to bardzo niewygodne. Podejście 2 w rzeczywistości nie jest możliwe do wdrożenia. Gdybyśmy mieli zadeklarować constexproperator literału ( ), to w jaki sposób określilibyśmy zwracany typ? Ponieważ potrzebujemy operatora, aby zwracał zmienną sekwencję znaków, musielibyśmy więc użyć const char*parametru do określenia typu zwracanego:

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

Powoduje to błąd kompilacji, ponieważ snie jest constexpr. Próba obejścia tego problemu poprzez wykonanie poniższych czynności niewiele pomaga.

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

Standard mówi, że ten konkretny operator literału jest zarezerwowany dla typów całkowitych i zmiennoprzecinkowych. Chociaż 123_szadziała, abc_snie zadziała . A co, jeśli całkowicie porzucimy literały zdefiniowane przez użytkownika i po prostu użyjemy zwykłej constexprfunkcji?

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

Tak jak poprzednio, napotykamy na problem polegający na tym, że tablica, teraz parametr constexprfunkcji, sama w sobie nie jest już constexprtypem.

Uważam, że powinno być możliwe zdefiniowanie makra preprocesora C, które pobiera ciąg i rozmiar ciągu jako argumenty i zwraca sekwencję składającą się ze znaków w ciągu (używając BOOST_PP_FOR, stringifikacji, indeksów tablicy i tym podobnych). Nie mam jednak czasu (lub wystarczającego zainteresowania) na zaimplementowanie takiego makra =)

void-pointer
źródło
2
Boost ma makro, które definiuje ciąg znaków, który może być używany jako wyrażenie stałe. Cóż, definiuje klasę, która ma element członkowski typu string. Sprawdziłeś to?
Pubby,
6
Czy sprawdziłeś cpp-next.com/archive/2012/10/… ?
Evgeny Panasyuk
1
Stack Overflow nie jest odpowiednim miejscem do zadawania pytań o to, czy istnieje propozycja czegoś. Najlepszym miejscem do tego byłaby witryna C ++ .
Nicol Bolas,
1
Zasadniczo rozszerzasz znaki przechowywane w tablicy / ptr do pakietu parametrów (tak jak zrobił to Xeo). Chociaż nie są one podzielone na argumenty szablonów innych niż typ, możesz ich używać w constexprfunkcjach i inicjować tablice (w związku z tym, concat, substr itp.).
dyp
1
@MareInfinitus Krótko mówiąc, constexprciągi mogą być analizowane podczas kompilacji, dzięki czemu można wybrać różne ścieżki kodu w zależności od wyników. Zasadniczo możesz tworzyć EDL w C ++; aplikacje są dość nieograniczone.
void-pointer

Odpowiedzi:

127

Nie widziałem nic, co mogłobystr_const pasować do elegancji Scotta Schurra prezentowanej na C ++ Now 2012 . To constexprjednak wymaga .

Oto, jak możesz go używać i co może zrobić:

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!
}

Nie ma nic fajniejszego niż sprawdzanie zakresu czasu kompilacji!

Zarówno użytkowanie, jak i implementacja są wolne od makr. I nie ma sztucznego ograniczenia rozmiaru struny. Opublikowałbym implementację tutaj, ale szanuję ukryte prawa autorskie Scotta. Realizacja znajduje się na jednym slajdzie jego prezentacji, do której link znajduje się powyżej.

Howard Hinnant
źródło
3
Czy operacje, które tworzą nowe ciągi constexpr (takie jak konkatenacja ciągów i wyodrębnianie podciągów) mogą działać z tym podejściem? Być może przy użyciu dwóch klas ciągów constexpr (jednej opartej, str_consta drugiej opartej sequence), może to być możliwe. Użytkownik użyłby str_constdo zainicjowania łańcucha, ale kolejne operacje, które utworzą nowe łańcuchy, zwróciłyby sequenceobiekty.
void-pointer
5
To dobry fragment kodu. Jednak to podejście nadal ma wadę w porównaniu do łańcucha zadeklarowanego z sekwencją znaków jako parametrami szablonu: str_const jest wartością stałą, a nie typem, co uniemożliwia użycie wielu idiomów metaprogramowania.
Jean-Bernard Jansen
1
@JBJansen, możliwe jest, bez funkcji skrótu, skompilowanie ciągu do typu, który może być następnie użyty jako parametr szablonu. Każdy inny ciąg daje inny typ. Podstawowym pomysłem jest przekształcenie łańcucha znaków w pakiet znaków template<char... cs>. Teoretycznie można zbudować coś, co pobiera literalny ciąg znaków i kompiluje zawartość do funkcji. Zobacz odpowiedź przez dyp. Bardzo kompletnie wyglądająca biblioteka jest metaparatem . Zasadniczo można zdefiniować dowolne mapowanie z ciągów literałów do typów i zaimplementować je za pomocą tego rodzaju technologii.
Aaron McDaid
1
Nie podzielam entuzjazmu… nie działa z metafunkcjami szablonów - bardzo irytujące z powodu głupiego kompromisu, że funkcje constexpr mogą być wywoływane w czasie wykonywania - brak prawdziwej konkatenacji, wymaga definicji tablicy znaków (brzydka w nagłówku) - chociaż to jest prawdziwe w przypadku większości rozwiązań bez makr, dzięki wspomnianemu kompromisowi constexpr - a sprawdzanie zakresu nie robi na mnie większego wrażenia, ponieważ ma to nawet niski constexpr const char *. Wywaliłem swój własny łańcuch parametrów, który można również utworzyć z literału (używając metafunkcji) kosztem definicji tablicy.
Arne Vogel,
2
@ user975326: Właśnie przejrzałem moją implementację tego i wygląda na to, że dodałem plik constexpr operator==. Przepraszam. Prezentacja Scotta powinna pomóc Ci zacząć, jak to zrobić. W C ++ 14 jest to dużo łatwiejsze niż w C ++ 11. Nie zawracałbym sobie głowy próbowaniem w C ++ 11. Zobacz najnowsze constexpr
wystąpienia
41

Uważam, że powinno być możliwe zdefiniowanie makra preprocesora C, które pobiera ciąg i rozmiar ciągu jako argumenty i zwraca sekwencję składającą się ze znaków w ciągu (przy użyciu BOOST_PP_FOR, stringification, tablicy wskaźników i tym podobnych). Nie mam jednak czasu (ani wystarczającego zainteresowania) na zaimplementowanie takiego makra

można to zaimplementować bez polegania na przyspieszeniu, używając bardzo prostego makra i niektórych funkcji C ++ 11:

  1. lambdy wariadyczne
  2. szablony
  3. uogólnione wyrażenia stałe
  4. niestatyczne inicjatory elementu członkowskiego danych
  5. jednolita inicjalizacja

(te dwa ostatnie nie są tutaj ściśle wymagane)

  1. Musimy być w stanie utworzyć instancję szablonu wariadycznego ze wskazaniami dostarczonymi przez użytkownika od 0 do N - narzędzie przydatne również na przykład do rozwinięcia krotki do argumentu funkcji szablonu wariadycznego (zobacz pytania: Jak rozwinąć krotkę w argumenty funkcji szablonu wariadycznego?
    " rozpakowywanie „krotki, aby wywołać odpowiedni wskaźnik funkcji )

    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. następnie zdefiniuj zmienny szablon o nazwie string z parametrem innym niż typ 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. teraz najciekawsza część - aby przekazać literały znaków do szablonu ciągu:

    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{};    \
        }()
    

prosta demonstracja konkatenacji pokazuje użycie:

    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;
    }

https://ideone.com/8Ft2xu

user1115339
źródło
1
To jest tak proste, że wciąż nie mogę uwierzyć, że działa. +1! Jedna rzecz: czy nie powinieneś używać size_t zamiast unsigned?
kirbyfan64sos
1
A co z używaniem operator+zamiast operator*? (str_hello + str_world)
Remy Lebeau
Wolę to rozwiązanie od popularnej metody str_const Scotta Schurra, ponieważ ta metoda zapewnia, że ​​dane bazowe są constexpr. Metoda Schurra pozwala mi stworzyć str_const w czasie wykonywania ze zmienną stosu char []; Nie mogę bezpiecznie zwrócić str_const z funkcji ani przekazać go do innego wątku.
Glenn
Link jest martwy ... czy ktoś może go opublikować ponownie? @Glenn?
einpoklum
Powinieneś dodać dodatkową parę nawiasów klamrowych wokół lambdy w swoim CSTRINGmakrze. W przeciwnym razie nie możesz utworzyć CSTRINGwywołania wewnętrznego do []operatora, ponieważ double [[są zarezerwowane dla atrybutów.
florestan
23

Edycja: jak zauważył Howard Hinnant (i ja nieco w moim komentarzu do OP), możesz nie potrzebować typu z każdym pojedynczym znakiem ciągu jako pojedynczym argumentem szablonu. Jeśli tego potrzebujesz, poniżej znajdziesz rozwiązanie bez makr.

Jest sztuczka, którą znalazłem, próbując pracować z ciągami znaków w czasie kompilacji. Wymaga wprowadzenia innego typu poza „ciągiem szablonów”, ale w ramach funkcji można ograniczyć zakres tego typu.

Nie używa makr, ale raczej niektóre funkcje C ++ 11.

#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);
}

// destination "template string" type
template < char... chars >
struct exploded_string
{
    static void print()
    {
        char const str[] = { chars... };
        std::cout.write(str, sizeof(str));
    }
};

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

    // recursion end
    template < typename StrProvider, char... chars >
    struct explode_impl < StrProvider, 0, chars... >
    {
         using result = exploded_string < chars... >;
    };

// syntactical sugar
template < typename StrProvider >
using explode =
    typename explode_impl < StrProvider,
                            c_strlen(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
    
    my_str.print();
}
 
dyp
źródło
1
Właśnie spędziłem weekend niezależnie rozwijając podobny fragment kodu i tworząc bardzo podstawowy system do parsowania ciągów znaków, np pair<int,pair<char,double>>. Byłem z siebie dumny, a potem odkryłem tę odpowiedź i dzisiejszą bibliotekę metaparse ! Naprawdę powinienem dokładniej przeszukać SO przed rozpoczęciem takich głupich projektów :-) Myślę, że teoretycznie można by zbudować kompilator w pełni C ++ z tego rodzaju technologii. Jaka jest najbardziej szalona rzecz, która została zbudowana w ten sposób?
Aaron McDaid
Nie wiem Nigdy tak naprawdę nie używałem tych technik w rzeczywistym projekcie, więc nie zastosowałem tego podejścia. Chociaż myślę, że pamiętam niewielką odmianę sztuczki typu lokalnego, która była nieco wygodniejsza… może lokalne zakłócenia char[].
dyp
Masz na myśli my_str.print();zamiast str.print();?
mike
Czy istnieje nieco krótsza wersja C ++ 14?
mike
1
Zamiast drukarki rekurencyjnej, myślę, że łatwiejszą opcją jest zrobienie tegochar str[] = {ttc...}; std::cout << str << std::endl;
Ajay Brahmakshatriya
11

Jeśli nie chcesz korzystać z rozwiązania Boost , możesz stworzyć proste makra, które zrobią coś podobnego:

#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!")>;

Jedynym problemem jest stały rozmiar 64 znaków (plus dodatkowe zero). Ale można go łatwo zmienić w zależności od potrzeb.

Yankes
źródło
1
Bardzo lubię to rozwiązanie; to bardzo proste i elegancko wykonuje swoją pracę. Czy można zmodyfikować makro tak, aby nic nie było dołączane sizeof(str) > i(zamiast dołączać dodatkowe 0,tokeny)? Łatwo jest zdefiniować trimmetafunkcję, która zrobi to po wywołaniu makra, ale byłoby dobrze, gdyby samo makro można było zmodyfikować.
void-pointer
Jest niemożliwe, ponieważ parser nie rozumie sizeof(str). Możliwe jest ręczne dodawanie rozmiaru ciągu, MACRO_GET_STR(6, "Hello")ale wymaga to makr Boost do działania, ponieważ ręczne pisanie wymaga 100 razy więcej kodu (potrzebujesz implementacji prostej rzeczy, takiej jak 1+1).
Yankes
6

Uważam, że powinno być możliwe zdefiniowanie makra preprocesora C, które pobiera ciąg i rozmiar ciągu jako argumenty i zwraca sekwencję składającą się ze znaków w ciągu (używając BOOST_PP_FOR, stringification, tablic subscripts i tym podobnych)

Jest artykuł: Używanie ciągów znaków w metaprogramach szablonów C ++ autorstwa Abla Sinkovicsa i Dave'a Abrahamsa.

Ma pewne ulepszenie w stosunku do twojego pomysłu użycia makra + BOOST_PP_REPEAT - nie wymaga przekazywania wyraźnego rozmiaru do makra. Krótko mówiąc, opiera się na ustalonym górnym limicie rozmiaru łańcucha i „ochronie przed przepełnieniem ciągu”:

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

plus warunkowe wzmocnienie :: mpl :: push_back .


Zmieniłem moją akceptowaną odpowiedź na rozwiązanie Yankesa, ponieważ rozwiązuje ten konkretny problem i robi to elegancko bez użycia constexpr lub złożonego kodu preprocesora.

Jeśli akceptujesz końcowe zera, ręcznie napisane pętle makr, 2x powtórzenie łańcucha w rozszerzonym makrze i nie masz Boost - zgadzam się - jest lepiej. Chociaż w przypadku Boost byłyby to tylko trzy linie:

DEMO NA ŻYWO

#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
Evgeny Panasyuk
źródło
Początkowo zmieniłem rozwiązanie na Yankes, ponieważ podał tutaj pierwszy działający przykład. W tym momencie jest wiele dobrych, konkurencyjnych pomysłów. To był mój błąd, wybierając odpowiedź tak wcześnie. Obecnie zaznaczam to pytanie jako bez odpowiedzi i wstrzymuję się, aż będę miał czas na wypróbowanie pomysłów, które wszyscy tutaj opublikowali. W odpowiedziach, których tu udzielili ludzie, jest wiele przydatnych informacji ...
void-pointer
Zgadzam się - na przykład podoba mi się przykład Howarda Hinnanta.
Evgeny Panasyuk
5

Kolega rzucił mi wyzwanie, aby połączyć ciągi znaków w pamięci w czasie kompilacji. Obejmuje również tworzenie instancji poszczególnych ciągów w czasie kompilacji. Pełna lista kodów znajduje się tutaj:

//Arrange strings contiguously in memory at compile-time from string literals.
//All free functions prefixed with "my" to faciliate grepping the symbol tree
//(none of them should show up).

#include <iostream>

using std::size_t;

//wrapper for const char* to "allocate" space for it at compile-time
template<size_t N>
struct String {
    //C arrays can only be initialised with a comma-delimited list
    //of values in curly braces. Good thing the compiler expands
    //parameter packs into comma-delimited lists. Now we just have
    //to get a parameter pack of char into the constructor.
    template<typename... Args>
    constexpr String(Args... args):_str{ args... } { }
    const char _str[N];
};

//takes variadic number of chars, creates String object from it.
//i.e. myMakeStringFromChars('f', 'o', 'o', '\0') -> String<4>::_str = "foo"
template<typename... Args>
constexpr auto myMakeStringFromChars(Args... args) -> String<sizeof...(Args)> {
    return String<sizeof...(args)>(args...);
}

//This struct is here just because the iteration is going up instead of
//down. The solution was to mix traditional template metaprogramming
//with constexpr to be able to terminate the recursion since the template
//parameter N is needed in order to return the right-sized String<N>.
//This class exists only to dispatch on the recursion being finished or not.
//The default below continues recursion.
template<bool TERMINATE>
struct RecurseOrStop {
    template<size_t N, size_t I, typename... Args>
    static constexpr String<N> recurseOrStop(const char* str, Args... args);
};

//Specialisation to terminate recursion when all characters have been
//stripped from the string and converted to a variadic template parameter pack.
template<>
struct RecurseOrStop<true> {
    template<size_t N, size_t I, typename... Args>
    static constexpr String<N> recurseOrStop(const char* str, Args... args);
};

//Actual function to recurse over the string and turn it into a variadic
//parameter list of characters.
//Named differently to avoid infinite recursion.
template<size_t N, size_t I = 0, typename... Args>
constexpr String<N> myRecurseOrStop(const char* str, Args... args) {
    //template needed after :: since the compiler needs to distinguish
    //between recurseOrStop being a function template with 2 paramaters
    //or an enum being compared to N (recurseOrStop < N)
    return RecurseOrStop<I == N>::template recurseOrStop<N, I>(str, args...);
}

//implementation of the declaration above
//add a character to the end of the parameter pack and recurse to next character.
template<bool TERMINATE>
template<size_t N, size_t I, typename... Args>
constexpr String<N> RecurseOrStop<TERMINATE>::recurseOrStop(const char* str,
                                                            Args... args) {
    return myRecurseOrStop<N, I + 1>(str, args..., str[I]);
}

//implementation of the declaration above
//terminate recursion and construct string from full list of characters.
template<size_t N, size_t I, typename... Args>
constexpr String<N> RecurseOrStop<true>::recurseOrStop(const char* str,
                                                       Args... args) {
    return myMakeStringFromChars(args...);
}

//takes a compile-time static string literal and returns String<N> from it
//this happens by transforming the string literal into a variadic paramater
//pack of char.
//i.e. myMakeString("foo") -> calls myMakeStringFromChars('f', 'o', 'o', '\0');
template<size_t N>
constexpr String<N> myMakeString(const char (&str)[N]) {
    return myRecurseOrStop<N>(str);
}

//Simple tuple implementation. The only reason std::tuple isn't being used
//is because its only constexpr constructor is the default constructor.
//We need a constexpr constructor to be able to do compile-time shenanigans,
//and it's easier to roll our own tuple than to edit the standard library code.

//use MyTupleLeaf to construct MyTuple and make sure the order in memory
//is the same as the order of the variadic parameter pack passed to MyTuple.
template<typename T>
struct MyTupleLeaf {
    constexpr MyTupleLeaf(T value):_value(value) { }
    T _value;
};

//Use MyTupleLeaf implementation to define MyTuple.
//Won't work if used with 2 String<> objects of the same size but this
//is just a toy implementation anyway. Multiple inheritance guarantees
//data in the same order in memory as the variadic parameters.
template<typename... Args>
struct MyTuple: public MyTupleLeaf<Args>... {
    constexpr MyTuple(Args... args):MyTupleLeaf<Args>(args)... { }
};

//Helper function akin to std::make_tuple. Needed since functions can deduce
//types from parameter values, but classes can't.
template<typename... Args>
constexpr MyTuple<Args...> myMakeTuple(Args... args) {
    return MyTuple<Args...>(args...);
}

//Takes a variadic list of string literals and returns a tuple of String<> objects.
//These will be contiguous in memory. Trailing '\0' adds 1 to the size of each string.
//i.e. ("foo", "foobar") -> (const char (&arg1)[4], const char (&arg2)[7]) params ->
//                       ->  MyTuple<String<4>, String<7>> return value
template<size_t... Sizes>
constexpr auto myMakeStrings(const char (&...args)[Sizes]) -> MyTuple<String<Sizes>...> {
    //expands into myMakeTuple(myMakeString(arg1), myMakeString(arg2), ...)
    return myMakeTuple(myMakeString(args)...);
}

//Prints tuple of strings
template<typename T> //just to avoid typing the tuple type of the strings param
void printStrings(const T& strings) {
    //No std::get or any other helpers for MyTuple, so intead just cast it to
    //const char* to explore its layout in memory. We could add iterators to
    //myTuple and do "for(auto data: strings)" for ease of use, but the whole
    //point of this exercise is the memory layout and nothing makes that clearer
    //than the ugly cast below.
    const char* const chars = reinterpret_cast<const char*>(&strings);
    std::cout << "Printing strings of total size " << sizeof(strings);
    std::cout << " bytes:\n";
    std::cout << "-------------------------------\n";

    for(size_t i = 0; i < sizeof(strings); ++i) {
        chars[i] == '\0' ? std::cout << "\n" : std::cout << chars[i];
    }

    std::cout << "-------------------------------\n";
    std::cout << "\n\n";
}

int main() {
    {
        constexpr auto strings = myMakeStrings("foo", "foobar",
                                               "strings at compile time");
        printStrings(strings);
    }

    {
        constexpr auto strings = myMakeStrings("Some more strings",
                                               "just to show Jeff to not try",
                                               "to challenge C++11 again :P",
                                               "with more",
                                               "to show this is variadic");
        printStrings(strings);
    }

    std::cout << "Running 'objdump -t |grep my' should show that none of the\n";
    std::cout << "functions defined in this file (except printStrings()) are in\n";
    std::cout << "the executable. All computations are done by the compiler at\n";
    std::cout << "compile-time. printStrings() executes at run-time.\n";
}
Átila Neves
źródło
Jesteś pewien, że jest to zrobione w czasie kompilacji? Nastąpiła dyskusja o tym jakiś czas temu, i do mnie, wynik nie jest jasne.
dyp
1
Bieganie objdump -t a.out |grep myniczego nie znajduje. Kiedy zacząłem pisać ten kod, eksperymentowałem z usuwaniem constexprz funkcji i objdumppokazywałem, kiedy constexprzostały pominięte. Mam 99,9% pewności, że dzieje się to w czasie kompilacji.
Átila Neves,
1
Jeśli spojrzysz na deasemblację ( -S), zauważysz, że gcc (4.7.2) rzeczywiście rozwiązuje constexprfunkcje w czasie kompilacji. Jednak ciągi nie są składane w czasie kompilacji. Raczej (jeśli zinterpretuję to poprawnie) dla każdego znaku z tych „zmontowanych” ciągów istnieje własna movboperacja, która jest prawdopodobnie optymalizacją, której szukałeś.
dyp
2
To prawda. Próbowałem ponownie z gcc 4.9 i nadal robi to samo. Zawsze myślałem, że kompilator jest głupi, a dopiero wczoraj pomyślałem, że powinienem wypróbować inny kompilator. Z clang, bajtów ruchu w ogóle nie ma. Z gcc, -Os też się ich pozbywa, ale -O3 robi to samo.
Átila Neves
5

Oto zwięzłe rozwiązanie w C ++ 14 do tworzenia std :: tuple <char ...> dla każdego przekazanego ciągu znaków czasu kompilacji.

#include <tuple>
#include <utility>


namespace detail {
        template <std::size_t ... indices>
        decltype(auto) build_string(const char * str, std::index_sequence<indices...>) {
                return std::make_tuple(str[indices]...);
        }
}

template <std::size_t N>
constexpr decltype(auto) make_string(const char(&str)[N]) {
        return detail::build_string(str, std::make_index_sequence<N>());
}

auto HelloStrObject = make_string("hello");

A oto jeden do tworzenia unikalnego typu czasu kompilacji, odciętego od drugiego wpisu makra.

#include <utility>

template <char ... Chars>
struct String {};

template <typename Str, std::size_t ... indices>
decltype(auto) build_string(std::index_sequence<indices...>) {
        return String<Str().chars[indices]...>();
}

#define make_string(str) []{\
        struct Str { const char * chars = str; };\
        return build_string<Str>(std::make_index_sequence<sizeof(str)>());\
}()

auto HelloStrObject = make_string("hello");

Naprawdę szkoda, że ​​nie można jeszcze do tego użyć literałów zdefiniowanych przez użytkownika.

kacey
źródło
Właściwie mogą używać rozszerzenia obsługiwanego przez GCC / Clang, ale zamierzam poczekać, zanim zostanie ono dodane do standardu, zanim opublikuję je jako odpowiedź.
void-pointer
5

Nikt nie lubi mojej drugiej odpowiedzi: - <. Więc tutaj pokazuję, jak przekonwertować str_const na rzeczywisty typ:

#include <iostream>
#include <utility>

// constexpr string with const member functions
class str_const { 
private:
    const char* const p_;
    const std::size_t sz_;
public:

    template<std::size_t N>
    constexpr str_const(const char(&a)[N]) : // ctor
    p_(a), sz_(N-1) {}

    constexpr char operator[](std::size_t n) const { 
        return n < sz_ ? p_[n] :
        throw std::out_of_range("");
    }

    constexpr std::size_t size() const { return sz_; } // size()
};


template <char... letters>
struct string_t{
    static char const * c_str() {
        static constexpr char string[]={letters...,'\0'};
        return string;
    }
};

template<str_const const& str,std::size_t... I>
auto constexpr expand(std::index_sequence<I...>){
    return string_t<str[I]...>{};
}

template<str_const const& str>
using string_const_to_type = decltype(expand<str>(std::make_index_sequence<str.size()>{}));

constexpr str_const hello{"Hello World"};
using hello_t = string_const_to_type<hello>;

int main()
{
//    char c = hello_t{};        // Compile error to print type
    std::cout << hello_t::c_str();
    return 0;
}

Kompiluje się z clang ++ -stdlib = libc ++ -std = c ++ 14 (clang 3.7)

Miły mężczyzna
źródło
Działa dobrze, ale nie dla msvc 2019, ponieważ narzeka, że ​​str.size () nie jest constexpr. Można to naprawić, dodając 2. przy użyciu oddzielnego dedukcji str.size (). Może to powstrzymało niektóre głosy za ;-)
Zacharias
2

na podstawie pomysłu Howarda Hinnanta możesz stworzyć klasę literałów, która doda do siebie dwa literały.

template<int>
using charDummy = char;

template<int... dummy>
struct F
{
    const char table[sizeof...(dummy) + 1];
    constexpr F(const char* a) : table{ str_at<dummy>(a)..., 0}
    {

    }
    constexpr F(charDummy<dummy>... a) : table{ a..., 0}
    {

    }

    constexpr F(const F& a) : table{ a.table[dummy]..., 0}
    {

    }

    template<int... dummyB>
    constexpr F<dummy..., sizeof...(dummy)+dummyB...> operator+(F<dummyB...> b)
    {
        return { this->table[dummy]..., b.table[dummyB]... };
    }
};

template<int I>
struct get_string
{
    constexpr static auto g(const char* a) -> decltype( get_string<I-1>::g(a) + F<0>(a + I))
    {
        return get_string<I-1>::g(a) + F<0>(a + I);
    }
};

template<>
struct get_string<0>
{
    constexpr static F<0> g(const char* a)
    {
        return {a};
    }
};

template<int I>
constexpr auto make_string(const char (&a)[I]) -> decltype( get_string<I-2>::g(a) )
{
    return get_string<I-2>::g(a);
}

constexpr auto a = make_string("abc");
constexpr auto b = a+ make_string("def"); // b.table == "abcdef" 
Yankes
źródło
skąd się str_atbierze
mic_e
jest coś takiego:str_at<int I>(const char* a) { return a[i]; }
Yankes
2

Twoje podejście nr 1 jest właściwe.

Jednak tablica musiałaby mieć zewnętrzne połączenie, więc aby podejście 1 działało, musielibyśmy napisać coś takiego: constexpr const char str [] = "Witaj, świecie!";

Nie, nieprawda. To kompiluje się z clang i gcc. Mam nadzieję, że to standardowe C ++ 11, ale nie jestem liderem języka.

#include <iostream>

template <char... letters>
struct string_t{
    static char const * c_str() {
        static constexpr char string[]={letters...,'\0'};
        return string;
    }
};

// just live with it, but only once
using Hello_World_t = string_t<'H','e','l','l','o',' ','w','o','r','l','d','!'>;

template <typename Name>
void print()
{
    //String as template parameter
    std::cout << Name::c_str();
}

int main() {
    std::cout << Hello_World_t::c_str() << std::endl;
    print<Hello_World_t>();
    return 0;
}

To, co naprawdę chciałbym dla c ++ 17, byłoby równoważne (aby ukończyć podejście # 1)

// for template <char...>
<"Text"> == <'T','e','x','t'>

Coś bardzo podobnego już istnieje w standardzie dla szablonów literałów zdefiniowanych przez użytkownika, o czym również wspomina void-pointer, ale tylko w przypadku cyfr. Do tego czasu kolejną małą sztuczką jest użycie trybu edycji z nadpisaniem + kopiowanie i wklejanie

string_t<' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '>;

Jeśli nie masz nic przeciwko makro, to działa (nieznacznie zmodyfikowane z odpowiedzi Yankes):

#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)

//CT_STR means Compile-Time_String
#define CT_STR(str) string_t<MACRO_GET_64(#str, 0), 0 >//guard for longer strings

print<CT_STR(Hello World!)>();
Miły mężczyzna
źródło
2

rozwiązanie firmy Kacey do tworzenia unikalnego typu w czasie kompilacji może, z niewielkimi modyfikacjami, być również używane z C ++ 11:

template <char... Chars>
struct string_t {};

namespace detail {
template <typename Str,unsigned int N,char... Chars>
struct make_string_t : make_string_t<Str,N-1,Str().chars[N-1],Chars...> {};

template <typename Str,char... Chars>
struct make_string_t<Str,0,Chars...> { typedef string_t<Chars...> type; };
} // namespace detail

#define CSTR(str) []{ \
    struct Str { const char *chars = str; }; \
    return detail::make_string_t<Str,sizeof(str)>::type(); \
  }()

Posługiwać się:

template <typename String>
void test(String) {
  // ... String = string_t<'H','e','l','l','o','\0'>
}

test(CSTR("Hello"));
uśmiechanie się
źródło
2

Podczas zabawy z mapą doładowania hana trafiłem na ten wątek. Ponieważ żadna z odpowiedzi nie rozwiązała mojego problemu, znalazłem inne rozwiązanie, które chcę tutaj dodać, ponieważ może być potencjalnie pomocne dla innych.

Mój problem polegał na tym, że podczas używania mapy boost hana z ciągami hana kompilator nadal generował kod runtime (patrz poniżej). Powodem było oczywiście to, że trzeba było odpytać mapę w czasie kompilacji constexpr. Nie jest to możliwe, ponieważ BOOST_HANA_STRINGmakro generuje lambdę, której nie można używać w constexprkontekście. Z drugiej strony mapa wymaga ciągów o różnej zawartości, aby były różnymi typami.

Ponieważ rozwiązania w tym wątku albo używają lambda, albo nie zapewniają różnych typów dla różnych treści, uznałem następujące podejście za pomocne. Ponadto unika hacky str<'a', 'b', 'c'>składni.

Podstawowym pomysłem jest posiadanie wersji str_constszablonu Scotta Schurra na podstawie skrótu postaci. Jest c++14, ale c++11powinno być możliwe przy rekurencyjnej implementacji crc32funkcji (patrz tutaj ).

// str_const from https://github.com/boostcon/cppnow_presentations_2012/blob/master/wed/schurr_cpp11_tools_for_class_authors.pdf?raw=true

    #include <string>

template<unsigned Hash>  ////// <- This is the difference...
class str_const2 { // constexpr string
private:
    const char* const p_;
    const std::size_t sz_;
public:
    template<std::size_t N>
    constexpr str_const2(const char(&a)[N]) : // ctor
        p_(a), sz_(N - 1) {}


    constexpr char operator[](std::size_t n) const { // []
        return n < sz_ ? p_[n] :
            throw std::out_of_range("");
    }

    constexpr std::size_t size() const { return sz_; } // size()

    constexpr const char* const data() const {
        return p_;
    }
};

// Crc32 hash function. Non-recursive version of https://stackoverflow.com/a/23683218/8494588
static constexpr unsigned int crc_table[256] = {
    0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
    0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
    0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
    0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
    0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
    0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
    0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
    0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
    0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
    0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
    0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
    0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
    0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
    0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
    0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
    0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
    0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
    0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
    0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
    0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
    0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
    0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
    0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
    0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
    0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
    0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
    0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
    0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
    0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
    0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
    0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
    0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
    0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
    0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
    0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
    0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
    0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
    0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
    0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
    0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
    0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
    0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
    0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};

template<size_t N>
constexpr auto crc32(const char(&str)[N])
{
    unsigned int prev_crc = 0xFFFFFFFF;
    for (auto idx = 0; idx < sizeof(str) - 1; ++idx)
        prev_crc = (prev_crc >> 8) ^ crc_table[(prev_crc ^ str[idx]) & 0xFF];
    return prev_crc ^ 0xFFFFFFFF;
}

// Conveniently create a str_const2
#define CSTRING(text) str_const2 < crc32( text ) >( text )

// Conveniently create a hana type_c<str_const2> for use in map
#define CSTRING_TYPE(text) hana::type_c<decltype(str_const2 < crc32( text ) >( text ))>

Stosowanie:

#include <boost/hana.hpp>

#include <boost/hana/map.hpp>
#include <boost/hana/pair.hpp>
#include <boost/hana/type.hpp>

namespace hana = boost::hana;

int main() {

    constexpr auto s2 = CSTRING("blah");

    constexpr auto X = hana::make_map(
        hana::make_pair(CSTRING_TYPE("aa"), 1)
    );    
    constexpr auto X2 = hana::insert(X, hana::make_pair(CSTRING_TYPE("aab"), 2));   
    constexpr auto ret = X2[(CSTRING_TYPE("aab"))];
    return ret;
}

Wynikowy kod asemblera w wersji clang-cl5.0 to:

012A1370  mov         eax,2  
012A1375  ret  
florestan
źródło
0

Chciałbym dodać dwa bardzo małe ulepszenia do odpowiedzi @ user1115339. Wspomniałem o nich w komentarzach do odpowiedzi, ale dla wygody umieszczę tutaj rozwiązanie kopiuj wklej.

Jedyną różnicą jest FIXED_CSTRINGmakro, które pozwala na użycie stringów w szablonach klas oraz jako argumentów do operatora indeksu (przydatne, jeśli masz np. Mapę kompilacji).

Przykład na żywo .

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;
    };
}

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];

    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{};    \
    }()


#define  FIXED_CSTRING(string_literal)                                                        \
    ([]{                                                                                 \
        struct  constexpr_string_type { const char * chars = string_literal; };         \
        return  typename variadic_toolbox::apply_range<sizeof(string_literal)-1,                 \
            compile_time::string_builder<constexpr_string_type>::template produce>::result{};    \
    }())    

struct A {

    auto test() {
        return FIXED_CSTRING("blah"); // works
        // return CSTRING("blah"); // works too
    }

    template<typename X>
    auto operator[](X) {
        return 42;
    }
};

template<typename T>
struct B {

    auto test() {       
       // return CSTRING("blah");// does not compile
       return FIXED_CSTRING("blah"); // works
    }
};

int main() {
    A a;
    //return a[CSTRING("blah")]; // fails with error: two consecutive ' [ ' shall only introduce an attribute before ' [ ' token
    return a[FIXED_CSTRING("blah")];
}
florestan
źródło
0

Moja własna implementacja jest oparta na podejściu z Boost.Hanałańcucha (klasa szablonu ze znakami o zmiennej liczbie znaków), ale wykorzystuje tylko C++11standard i constexprfunkcje ze ścisłym sprawdzaniem zgodności (byłby to błąd czasu kompilacji, gdyby nie wyrażenie czasu kompilacji). Można go zbudować ze zwykłego nieprzetworzonego łańcucha C zamiast fantazyjnego {'a', 'b', 'c' }(za pomocą makra).

Wdrożenie: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/tackle/tmpl_string.hpp

Testy: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/src/tests/unit/test_tmpl_string.cpp

Przykłady użycia:

const auto s0    = TACKLE_TMPL_STRING(0, "012");            // "012"
const char c1_s0 = UTILITY_CONSTEXPR_GET(s0, 1);            // '1'

const auto s1    = TACKLE_TMPL_STRING(0, "__012", 2);       // "012"
const char c1_s1 = UTILITY_CONSTEXPR_GET(s1, 1);            // '1'

const auto s2    = TACKLE_TMPL_STRING(0, "__012__", 2, 3);  // "012"
const char c1_s2 = UTILITY_CONSTEXPR_GET(s2, 1);            // '1'

// TACKLE_TMPL_STRING(0, "012") and TACKLE_TMPL_STRING(1, "012")
//   - semantically having different addresses.
//   So id can be used to generate new static array class field to store
//   a string bytes at different address.

// Can be overloaded in functions with another type to express the compiletimeness between functions:

template <uint64_t id, typename CharT, CharT... tchars>
const overload_resolution_1 & test_overload_resolution(const tackle::tmpl_basic_string<id, CharT, tchars...> &);
template <typename CharT>
const overload_resolution_2 & test_overload_resolution(const tackle::constexpr_basic_string<CharT> &);

// , where `constexpr_basic_string` is another approach which loses
//   the compiletimeness between function signature and body border,
//   because even in a `constexpr` function the compile time argument
//   looses the compiletimeness nature and becomes a runtime one.

Szczegóły dotyczące constexprgranicy czasu kompilacji funkcji: https://www.boost.org/doc/libs/1_65_0/libs/hana/doc/html/index.html#tutorial-appendix-constexpr

Więcej szczegółów dotyczących użytkowania można znaleźć w testach.

Cały projekt jest obecnie eksperymentalny.

Andry
źródło
0

W C ++ 17 z pomocniczą funkcją makro łatwo jest tworzyć ciągi czasu kompilacji:

template <char... Cs>
struct ConstexprString
{
    static constexpr int size = sizeof...( Cs );
    static constexpr char buffer[size] = { Cs... };
};

template <char... C1, char... C2>
constexpr bool operator==( const ConstexprString<C1...>& lhs, const ConstexprString<C2...>& rhs )
{
    if( lhs.size != rhs.size )
        return false;

    return std::is_same_v<std::integer_sequence<char, C1...>, std::integer_sequence<char, C2...>>;
}




template <typename F, std::size_t... Is>
constexpr auto ConstexprStringBuilder( F f, std::index_sequence<Is...> )
{
    return ConstexprString<f( Is )...>{};
}

#define CONSTEXPR_STRING( x )                                              \
  ConstexprStringBuilder( []( std::size_t i ) constexpr { return x[i]; },  \
                 std::make_index_sequence<sizeof(x)>{} )

A to jest przykład użycia:

auto n = CONSTEXPR_STRING( "ab" );
auto m = CONSTEXPR_STRING( "ab" );


static_assert(n == m);
zurrutik
źródło