Wygodne deklarowanie łańcuchów czasu kompilacji w C++
Możliwość tworzenia i manipulowania ciągami znaków podczas kompilacji w C++ ma kilka przydatnych aplikacji. Chociaż możliwe jest tworzenie łańcuchów w czasie kompilacji w C++, Proces jest bardzo uciążliwy, ponieważ łańcuch 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 łączenie łańcuchów, ekstrakcja podłańcuchów i wiele innych, mogą być łatwo zaimplementowane jako operacje na sekwencjach znaków. Czy Można zadeklarować ciągi kompilacji wygodniej? Jeśli nie, to czy w pracach jest jakaś propozycja, która pozwoliłaby na wygodną deklarację łańcuchów kompilacji?
Dlaczego Istniejące Podejścia Zawodzą
Najlepiej byłoby, gdybyśmy mogli zadeklarować ciągi czasu kompilacji w następujący sposób:
// Approach 1
using str1 = sequence<"Hello, world!">;
W tym celu należy wykonać następujące czynności:]}
// Approach 2
constexpr auto str2 = "Hello, world!"_s;
Gdzie decltype(str2)
miałby constexpr
konstruktor. Możliwa jest realizacja wersji Messiera podejścia 1, korzystając z faktu, ż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 łącze, więc aby podejście 1 zadział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 nie jest w rzeczywistości możliwe do wdrożenia. Gdybyśmy mieli zadeklarować (constexpr
) operator dosłowny, to jak określilibyśmy typ zwracany? Ponieważ operator musi zwracać zmienną sekwencję znaków, więc będziemy musieli użyć const char*
parametr określający typ powrotu:
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, wykonując następujące czynności, nie pomaga zbyt wiele.
template <char... Ts>
constexpr sequence<Ts...> operator"" _s() { return {}; }
Standard nakazuje, że ta konkretna literalna forma operatora jest zarezerwowana dla typów całkowitych i zmiennoprzecinkowych. Podczas gdy 123_s
zadziała, abc_s
nie zadziała. Co zrobić, jeśli porzucimy literały zdefiniowane przez użytkownika i po prostu użyjemy zwykłej funkcji constexpr
?
template <unsigned Size>
constexpr auto
string(const char (&array)[Size]) -> /* Some metafunction using `array` */
Jak poprzednio, biegniemy do problemu, że tablica, obecnie parametr funkcji constexpr
, nie jest już typem constexpr
.
Uważam, że powinno być możliwe zdefiniowanie makra preprocesora C, które przyjmuje łańcuch i rozmiar łańcucha jako argumenty i zwraca sekwencję składającą się ze znaków w łańcuchu (używając BOOST_PP_FOR
, stringification, array subscripts, i tym podobne). Jednak nie mam czasu (ani wystarczającego zainteresowania) aby zaimplementować takie makro =)
12 answers
Nie widziałem niczego, co by pasowało do elegancji Scotta Schurra.str_const
zaprezentowane na C++ Now 2012 . Wymaga jednak constexpr
.
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!
}
It doesn ' t get much cooler than compile-time range checking!
Zarówno użycie, jak i implementacja są wolne od makr. I nie ma sztucznego limitu rozmiaru Sznurka. Zamieściłbym implementację tutaj, ale szanuję ukryte prawa autorskie Scotta. Na realizacja znajduje się na jednym slajdzie z jego prezentacji powiązanej z powyższym.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-07 15:08:43
Uważam, że powinno być możliwe zdefiniowanie makra preprocesora C, które pobiera łańcuch i jego rozmiar jako argumenty i zwraca Sekwencja składająca się ze znaków w łańcuchu (za pomocą BOOST_PP_FOR, stringification, array subscripts i tym podobne). Nie mam jednak czasu (ani wystarczającego zainteresowania) na wdrożenie takich makro
Możliwe jest zaimplementowanie tego bez polegania na boost, przy użyciu bardzo prostego makra i niektórych z C++11 cechy:
- lambda variadic
- szablony
- uogólnione wyrażenia stałe
- niestatyczne inicjalizatory członków danych
- jednolita inicjalizacja
(dwa ostatnie nie są tu ściśle wymagane)
-
Musimy być w stanie utworzyć instancję zmiennego szablonu z wskazówkami dostarczonymi przez Użytkownika od 0 do N - narzędzie przydatne na przykład do rozszerzenia krotki na argument zmiennej funkcji szablonu (patrz pytania: Jak rozwinąć krotkę na zmienne argumenty funkcji szablonu?
"unpacking" krotka do wywołania pasującego wskaźnika 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 non-type parametr 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ęść-przekazanie literałów znaków do ciągu szablon:
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 sposób użycia:
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;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:17:58
Edit: jak zauważył Howard Hinnant (i ja nieco w moim komentarzu do OP), możesz nie potrzebować typu z każdym pojedynczym znakiem łańcucha jako pojedynczym argumentem szablonu. Jeśli tego potrzebujesz, poniżej znajdziesz rozwiązanie bez makro.
Jest pewna sztuczka, którą znalazłem podczas pracy z ciągami w czasie kompilacji. Wymaga wprowadzenia innego typu poza "łańcuchem 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);
}
// 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
my_str.print();
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-26 07:29:52
Jeśli nie chcesz używać Boost solution {[3] } możesz utworzyć proste makro, 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.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:39
Uważam, że powinno być możliwe zdefiniowanie makra preprocesora C, które przyjmuje łańcuch i rozmiar łańcucha jako argumenty i zwraca sekwencję składającą się ze znaków w łańcuchu (używając BOOST_PP_FOR, stringification, array subscripts i tym podobne)
Jest Artykuł: używanie łańcuchów w metaprogramach szablonów C++ autorstwa Abela Sinkovicsa i Dave ' a Abrahamsa.
Ma pewną poprawę w stosunku do twojego pomysłu użycia makra + BOOST_PP_REPEAT - nie wymaga przekazywania jawnego rozmiaru do makra. W skrócie, opiera się na stałym górnym limicie dla rozmiaru łańcucha i "ochrony przed przekroczeniem łańcucha":
template <int N>
constexpr char at(char const(&s)[N], int i)
{
return i >= N ? '\0' : s[i];
}
Plus warunkowe boost:: mpl:: push_back.
Zmieniłem zaakceptowaną odpowiedź na rozwiązanie Yankesa, ponieważ rozwiązuje ono 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 pisane makro, 2x powtarzanie ciągu w rozszerzonym makrze, a nie mieć Boost - to się Zgadzam - jest lepiej. Chociaż z Boostem byłyby to tylko trzy linijki:
LIVE DEMO
#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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-16 01:35:36
Kolega wyzwał mnie do łączenia łańcuchów w pamięci podczas kompilacji. Zawiera również tworzenie instancji poszczególnych łańcuchó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";
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-09 13:07:21
Na podstawie pomysłu z Howarda Hinnanta można utworzyć klasę literalną, która będzie dodawać dwa literały razem.
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"
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:33
Oto zwięzłe rozwiązanie C++14 do tworzenia STD:: tuple dla każdego przekazanego ciągu w czasie 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 kompilacji, przyciętego z drugiego posta 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");
Szkoda, że literały zdefiniowane przez użytkownika nie mogą być jeszcze Używane do tego celu.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-20 02:27:09
Chyba nikt nie lubi mojej drugiej odpowiedzi : -<.>
#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 z clang++ - stdlib=libc++ - std=c++14 (clang 3.7)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-16 12:37:16
Podczas gry na mapie boost hana natknąłem się na ten wątek. Ponieważ żadna z odpowiedzi nie rozwiązała mojego problemu, znalazłem inne rozwiązanie, które chcę dodać tutaj, ponieważ może być potencjalnie pomocne dla innych.
Mój problem polegał na tym, że podczas korzystania z mapy boost hana z łańcuchami hana, kompilator nadal generował jakiś kod uruchomieniowy(patrz niżej). Powodem było oczywiście to, że aby odpytywać mapę w czasie kompilacji, musi to być constexpr
. Nie jest to możliwe, ponieważ makro BOOST_HANA_STRING
generuje lambda, które nie mogą być użyte w kontekście constexpr
. 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 dostarczają różnych typów dla różnych treści, uznałem następujące podejście za pomocne. Ponadto unika się hacky str<'a', 'b', 'c'>
składni.
str_const
wykreślonej na haszu znaków. Jest c++14
, ale c++11
powinno być możliwe z rekurencyjna implementacja funkcji crc32
(Zobacz 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 ))>
Użycie:
#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 z clang-cl
5.0 to:
012A1370 mov eax,2
012A1375 ret
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-02 17:12:32
Twoje podejście # 1 jest poprawne.
Nie, Nie zgadza się. To kompiluje się z clang i gcc. Mam nadzieję, że jego standard c++11, ale nie jestem layerem języka.Jednak tablica musiałaby mieć zewnętrzne łącze, więc aby approach 1 zadziałał, musielibyśmy napisać coś takiego: constexpr const char str [] = " Hello, world!";
#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 następujące, aby być równoważne (do pełnego podejścia #1)
// for template <char...>
<"Text"> == <'T','e','x','t'>
Coś bardzo podobnego istnieje już w standardzie dla szablonowych liter zdefiniowanych przez użytkownika, o czym wspomina void-pointer, ale tylko dla cyfr. Do tego czasu kolejną sztuczką jest użycie trybu nadpisywania + Kopiuj i wklej
string_t<' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '>;
Jeśli nie przeszkadza ci makro, to działa (slighty zmodyfikowane z Yankes odpowiedz):
#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!)>();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-20 00:03:52
Rozwiązanie Kacey ' ego do tworzenia unikalnego typu 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(); \
}()
Użycie:
template <typename String>
void test(String) {
// ... String = string_t<'H','e','l','l','o','\0'>
}
test(CSTR("Hello"));
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-28 16:34:22