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 constexpr
konstruktora. 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ć constexpr
operator 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ż s
nie 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_s
zadziała, abc_s
nie zadziała . A co, jeśli całkowicie porzucimy literały zdefiniowane przez użytkownika i po prostu użyjemy zwykłej constexpr
funkcji?
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 constexpr
funkcji, sama w sobie nie jest już constexpr
typem.
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 =)
źródło
constexpr
funkcjach i inicjować tablice (w związku z tym, concat, substr itp.).constexpr
cią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.Odpowiedzi:
Nie widziałem nic, co mogłoby
str_const
pasować do elegancji Scotta Schurra prezentowanej na C ++ Now 2012 . Toconstexpr
jednak 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.
źródło
str_const
a drugiej opartejsequence
), może to być możliwe. Użytkownik użyłbystr_const
do zainicjowania łańcucha, ale kolejne operacje, które utworzą nowe łańcuchy, zwróciłybysequence
obiekty.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.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 najnowszeconstexpr
można to zaimplementować bez polegania na przyspieszeniu, używając bardzo prostego makra i niektórych funkcji C ++ 11:
(te dwa ostatnie nie są tutaj ściśle wymagane)
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; }; }
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]; }
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
źródło
operator+
zamiastoperator*
?(str_hello + str_world)
CSTRING
makrze. W przeciwnym razie nie możesz utworzyćCSTRING
wywołania wewnętrznego do[]
operatora, ponieważ double[[
są zarezerwowane dla atrybutów.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(); }
źródło
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?char[]
.my_str.print();
zamiaststr.print();
?char str[] = {ttc...}; std::cout << str << std::endl;
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.
źródło
sizeof(str) > i
(zamiast dołączać dodatkowe0,
tokeny)? Łatwo jest zdefiniowaćtrim
metafunkcję, która zrobi to po wywołaniu makra, ale byłoby dobrze, gdyby samo makro można było zmodyfikować.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 jak1+1
).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 .
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
źródło
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"; }
źródło
objdump -t a.out |grep my
niczego nie znajduje. Kiedy zacząłem pisać ten kod, eksperymentowałem z usuwaniemconstexpr
z funkcji iobjdump
pokazywałem, kiedyconstexpr
zostały pominięte. Mam 99,9% pewności, że dzieje się to w czasie kompilacji.-S
), zauważysz, że gcc (4.7.2) rzeczywiście rozwiązujeconstexpr
funkcje 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łasnamovb
operacja, która jest prawdopodobnie optymalizacją, której szukałeś.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.
źródło
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)
źródło
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"
źródło
str_at
bierzestr_at<int I>(const char* a) { return a[i]; }
Twoje podejście nr 1 jest właściwe.
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!)>();
źródło
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"));
źródło
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_STRING
makro generuje lambdę, której nie można używać wconstexpr
kontekś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_const
szablonu Scotta Schurra na podstawie skrótu postaci. Jestc++14
, alec++11
powinno być możliwe przy rekurencyjnej implementacjicrc32
funkcji (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-cl
5.0 to:012A1370 mov eax,2 012A1375 ret
źródło
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_CSTRING
makro, 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")]; }
źródło
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 tylkoC++11
standard iconstexpr
funkcje 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
constexpr
granicy czasu kompilacji funkcji: https://www.boost.org/doc/libs/1_65_0/libs/hana/doc/html/index.html#tutorial-appendix-constexprWięcej szczegółów dotyczących użytkowania można znaleźć w testach.
Cały projekt jest obecnie eksperymentalny.
źródło
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);
źródło