Зручно оголошувати рядки компіляційного часу в C ++


137

Уміння створювати та маніпулювати рядками під час компіляції в C ++ має кілька корисних програм. Хоча в C ++ можна створити рядки часу компіляції, процес дуже громіздкий, оскільки рядок потрібно оголосити як різноманітну послідовність символів, наприклад

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

Такі операції, як об'єднання рядків, вилучення підрядків та багато інших, можуть бути легко реалізовані як операції над послідовностями символів. Чи можна більш зручно оголошувати рядки часу компіляції? Якщо ні, то чи є у творах пропозиція, яка б дозволила зручно оголосити рядки часу компіляції?

Чому існуючі підходи не вдається

В ідеалі ми хотіли б мати можливість оголошувати рядки часу компіляції наступним чином:

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

або, використовуючи визначені користувачем літерали,

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

де decltype(str2)мав би constexprконструктор. Месіє версію підходу 1 можна реалізувати, скориставшись тим, що ви можете зробити наступне:

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

Однак масив повинен мати зовнішній зв'язок, тому для отримання підходу 1 до роботи нам потрібно буде написати щось подібне:

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

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

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

Потрібно говорити, що це дуже незручно. Підхід 2 насправді неможливо реалізувати. Якби ми оголосили ( constexpr) буквальний оператор, то як би ми вказали тип повернення? Оскільки нам потрібен оператор для повернення варіативної послідовності символів, тому нам потрібно буде використовувати const char*параметр для визначення типу повернення:

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

Це призводить до помилки компіляції, оскільки sце не є constexpr. Спроба обійти це, зробивши наступне, не дуже допомагає.

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

Стандарт диктує, що ця специфічна буквальна форма оператора зарезервована для цілих чисел і типів з плаваючою комою. Поки 123_sпрацював abc_sби, ні. Що робити, якщо ми взагалі скинемо визначені користувачем літерали і просто використаємо звичайну constexprфункцію?

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

Як і раніше, ми стикаємося з проблемою, що масив, тепер параметр constexprфункції, сам по собі вже не є constexprтипом.

Я вважаю, що має бути можливим визначити макрос препроцесора C, який приймає рядок і розмір рядка як аргументи, і повертає послідовність, що складається з символів у рядку (використовуючи BOOST_PP_FOR, струфікацію, підписи на масив тощо). Однак у мене немає часу (або достатньо інтересу), щоб реалізувати такий макрос =)


2
Boost має макрос, який визначає рядок, який можна використовувати як постійний вираз. Ну, він визначає клас, який має рядок. Ви це перевірили?
Паббі


1
Переповнення стека не є відповідним місцем для запитання про те, чи існує пропозиція про щось. Найкращим місцем для цього був би сайт C ++ .
Нікол Болас

1
В основному ви розширюєте символи, збережені в масиві / ptr, в пакет параметрів (як Xeo зробив). Хоча вони не розбиті на аргументи нетипових шаблонів, ви можете використовувати їх у межах constexprфункцій та ініціалізувати масиви (отже, concat, substr тощо).
dyp

1
@MareInfinitus Коротше кажучи, constexprрядки можна розбирати під час компіляції, так що ви можете приймати різні кодові шляхи залежно від результатів. По суті, ви можете створювати EDL в межах C ++; програми досить безмежні.
void-pointer

Відповіді:


127

Я не бачив нічого, що б відповідало елегантності Скотта Шурра,str_const представленого на C ++ Now 2012 . Це все ж вимагає constexpr.

Ось як ви можете ним користуватися та що це може зробити:

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

Це не набагато крутіше, ніж перевірка діапазону часу компіляції!

І використання, і реалізація не містять макросів. І штучного обмеження розміру рядків немає. Я б розмістив реалізацію тут, але я поважаю непрямі авторські права Скотта. Реалізація відбувається на одному слайді його презентації, пов'язаному вище.


3
Чи можуть операції, що створюють нові рядки contexpr (наприклад, об'єднання рядків та вилучення підрядків), працювати з таким підходом? Можливо, за допомогою двох класів рядків contexpr (один на основі, str_constа другий на основі sequence), це можливо. Користувач використовуватиме str_constдля ініціалізації рядка, але наступні операції, що створюють нові рядки, повертають sequenceоб'єкти.
void-pointer

5
Це хороший фрагмент коду. Однак у цього підходу все ж є недолік порівняно із рядком, оголошеним із символьною послідовністю як параметри шаблону: str_const - це постійне значення, а не тип, що запобігає використанню безлічі ідіом метапрограмування.
Жан-Бернард Янсен

1
@JBJansen, без хеш-функцій можна скласти рядок до типу, який потім може бути використаний як параметр шаблону. Кожен різний рядок дає різний тип. Основна ідея - перетворити рядок у пакет символів template<char... cs>. Теоретично ви можете побудувати щось, що займає буквальний рядок і компілює вміст до функції. Дивіться відповідь за допомогою dyp. Дуже повноцінна бібліотека - метапарзація . По суті, ви можете визначити будь-яке відображення від буквальних рядків до типів та реалізувати його за допомогою цієї технології.
Аарон Мак-Дейд

1
Я не поділяю ентузіазму ... не працює з метафункціями шаблонів - дуже дратує через дурний компроміс, що функції constexpr повинні бути викликані під час виконання - немає справжнього об'єднання, вимагає визначення масиву char (некрасиво у заголовку) - хоча це це стосується більшості макроелектричних рішень завдяки вищезгаданому компромісу constexpr - і перевірка діапазону мене не вражає, тому що навіть низький показник contexpr const char * має це. Я прокатував власний рядок пакета параметрів, який також можна зробити з літералу (використовуючи метафункцію) ціною визначення масиву.
Арне Фогель

2
@ user975326: Я щойно переглянув мою реалізацію цього, і схоже, що я додав constexpr operator==. Вибачте. Презентація Скотта повинна почати починати, як це зробити. На C ++ 14 набагато простіше, ніж у C ++ 11. Я б навіть не намагався пробувати C ++ 11. Дивіться останні constexprпереговори Скотта тут: youtube.com/user/CppCon
Говард Хіннант

41

Я вважаю, що має бути можливим визначити макрос препроцесора C, який приймає рядок та розмір рядка як аргументи, і повертає послідовність, що складається з символів у рядку (використовуючи BOOST_PP_FOR, строфікацію, підписи масиву тощо). Однак у мене немає часу (або достатньо інтересу) на реалізацію такого макросу

реалізувати це можна, не покладаючись на прискорення, використовуючи дуже простий макрос та деякі функції C ++ 11:

  1. лямбди різноманітні
  2. шаблони
  3. узагальнені постійні вирази
  4. нестатичні ініціалізатори учасників даних
  5. рівномірна ініціалізація

(останні два тут категорично не потрібні)

  1. нам потрібно мати можливість інстанціювати варіативний шаблон із поставленими користувачем показниками від 0 до N - інструмент, який також корисний, наприклад, для розширення кортежу на аргумент функції варіативного шаблону (див. питання: Як розгорнути кортеж на аргументи функції варіативного шаблону?
    " розпакування "кортежу для виклику відповідного вказівника функції )

    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. то визначте варіативний шаблон під назвою рядок з нетиповим параметром 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. Тепер найцікавіша частина - передати букве символів у шаблон рядка:

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

проста демонстрація конкатенації показує використання:

    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


1
Це настільки просто, що я досі не можу повірити, що це працює. +1! Одне: чи не слід використовувати size_t замість непідписаного?
kirbyfan64sos

1
А як щодо використання operator+замість operator*? (str_hello + str_world)
Ремі Лебо

Я віддаю перевагу цьому рішенню над популярним методом str_const популярного Скотта Шурра, оскільки цей метод гарантує, що базові дані - constexpr. Метод Шурра дозволяє мені створювати str_const під час виконання за допомогою змінної стека char []; Я не можу безпечно повернути str_const з функції або передати її в інший потік.
Гленн

Посилання мертве ... хтось може її репоставити? @Glenn?
einpoklum

Додайте додаткову пару брекетів навколо лямбда у свій CSTRINGмакрос. В іншому випадку ви не можете створити CSTRINGвнутрішній дзвінок []оператору, оскільки подвійні [[зарезервовані для атрибутів.
florestan

21

Редагувати: як зазначив Говард Хінант (і я дещо в коментарі до ОП), вам може не знадобитися тип із кожним символом рядка як аргумент одного шаблону. Якщо вам це потрібно, нижче є рішення, що не містить макросів.

Я знайшов хитрість, намагаючись працювати зі струнами під час компіляції. Потрібно ввести ще один тип, крім "рядка шаблону", але в межах функцій ви можете обмежити область цього типу.

Він не використовує макроси, а деякі функції 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();
}

1
Я щойно провів вихідні, самостійно розробивши подібний фрагмент коду та створивши дуже базову систему для розбору рядків типу, наприклад pair<int,pair<char,double>>. Я пишався собою і тоді виявив цю відповідь, і бібліотеку метапарза сьогодні! Мені справді слід більш ретельно шукати SO перед тим, як почати такі дурні проекти :-) Я здогадуюсь, що теоретично з цього виду технологій можна було б побудувати повністю компілятор C ++. Що найбезглуздішого, що з цим будується?
Аарон Макдейд

Не знаю. Я ніколи не використовував ці методи в реальному проекті, тому не дотримувався підходу. Хоча я думаю, що я пам’ятаю невелику зміну трюку місцевого типу, який був дещо зручнішим .. можливо місцева статика char[].
dyp

Ви маєте на увазі my_str.print();замість str.print();?
Майк

Чи є у С ++ 14 трохи коротша версія?
Майк

Прикро, що вам доведеться зробити провайдера (принаймні на C ++ 11) - Я дуже хотів би мати можливість використовувати рядок у тому ж самому твердженні: /
Alec Teal

10

Якщо ви не хочете використовувати рішення Boost, ви можете створити прості макроси, які будуть робити щось подібне:

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

Єдина проблема - фіксований розмір 64 символів (плюс додатковий нуль). Але це можна легко змінити залежно від ваших потреб.


Мені це рішення дуже подобається; це дуже просто і робить роботу елегантно. Чи можна змінити макрос так, щоб нічого не було додано sizeof(str) > i(замість додавання зайвих 0,лексем)? Неважко визначити trimметафункцію, яка зробить це після того, як макрос вже був викликаний, але було б добре, якби сам макрос змінено.
void-pointer

Це неможливо, тому що парсер не розуміє sizeof(str). Можна вручну додати розмір рядка типу, MACRO_GET_STR(6, "Hello")але для цього потрібні макроси Boost, оскільки для їх написання вручну потрібен 100 разів більше коду (потрібні такі прості речі, як 1+1).
Yankes

6

Я вважаю, що має бути можливим визначити макрос препроцесора C, який приймає рядок та розмір рядка як аргументи, і повертає послідовність, що складається з символів у рядку (використовуючи BOOST_PP_FOR, строфікацію, підписи масиву тощо)

Є стаття: Використання рядків у метапрограмах шаблону C ++ від Абеля Сіньковіка та Дейва Абрахамса.

Це має певне вдосконалення щодо вашої ідеї використання макроса + BOOST_PP_REPEAT - не потрібно передавати явний розмір макросу. Коротше кажучи, вона заснована на фіксованій верхній межі для розміру рядка та "захисті від перенапруги":

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

плюс умовне збільшення :: mpl :: push_back .


Я змінив свою прийняту відповідь на рішення Yankes, оскільки вона вирішує цю конкретну проблему, і робить це елегантно без використання constexpr або складного коду препроцесора.

Якщо ви приймаєте кінцеві нулі, рукописний цикл макросів, повторення рядка в розширеному макросі, і у вас немає Boost - тоді я згоден - це краще. Хоча з Boost це було б лише три лінії:

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

Я спочатку змінив рішення на Yankes ', оскільки він наводив тут перший робочий приклад. На даний момент існує багато хороших конкуруючих ідей. Це було моєю помилкою, коли я відповідав так рано. На даний момент я зауважу це питання як без відповіді, і затримаюсь, поки не знайду час спробувати ідеї, які тут розмістили всі. Тут є багато корисної інформації у відповідях, які люди дали тут ...
void-pointer

Я згоден - наприклад, мені подобається приклад Говарда Хінанна.
Євген Панасюк

5

Ніхто не любить мою іншу відповідь: - <. Тому тут я показую, як перетворити str_const в реальний тип:

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

Компілює з clang ++ -stdlib = libc ++ -std = c ++ 14 (clang 3.7)


Працює добре, але не для msvc 2019, оскільки він скаржиться на те, що str.size () не є constexpr. Можна виправити, додавши другий, використовуючи окремо виведення str.size (). Можливо, це стримувало деякі оновлення ;-)
Захарій

4

Ось короткий C ++ 14 рішення для створення std :: tuple <char ...> для кожного пройденого рядка компіляції.

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

І ось один для створення унікального типу компіляції часу, обробленого з іншого макросайта.

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

Це дуже погано, що визначені користувачем літерали для цього ще не можуть користуватися.


Насправді вони можуть використовувати розширення, що підтримується GCC / Clang, але я зачекаю, перш ніж це буде додано до стандарту, перш ніж опублікувати його як відповідь.
void-pointer

3

Колега кинув мені виклик об'єднати рядки в пам'яті під час компіляції. Він включає інстанціювання окремих рядків і під час компіляції. Повний перелік кодів тут:

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

Ви впевнені, що це робиться під час компіляції? Про це дискутували певний час тому, і для мене результат не зрозумілий.
dyp

Біг objdump -t a.out |grep myнічого не знаходить. Коли я почав вводити цей код, я продовжував експериментувати з видаленням constexprз функцій і objdumpпоказував їх, коли їх constexprбуло пропущено. Я на 99,9% впевнений, що це відбувається під час компіляції.
Átila Neves

1
Якщо ви подивитесь на демонтаж ( -S), ви помітите, що gcc (4.7.2) дійсно вирішує constexprфункції під час компіляції. Тим не менш, рядки не збираються під час компіляції. Швидше, (якщо я правильно його інтерпретую) для кожної таблиці цих "зібраних" рядків, є власна movbоперація, яка, мабуть, оптимізація, яку ви шукали.
dyp

2
Це правда. Я спробував ще раз з gcc 4.9, і це все одно робить те саме. Я завжди думав, що цей компілятор дурний, хоча вчора я думав спробувати інший компілятор. З клангом мотоциклів, що перебувають у байдарці, взагалі немає. З gcc, -O позбується і їх, але -O3 робить те ж саме.
Атіла Невес

2

на основі ідеї Говарда Хінаннта можна створити буквальний клас, який додасть два літерали разом.

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" 

звідки str_atберуться?
mic_e

його щось подібне:str_at<int I>(const char* a) { return a[i]; }
Yankes

2

Ваш підхід №1 є правильним.

Однак масив повинен мати зовнішній зв'язок, тому для отримання підходу 1 до роботи нам доведеться написати щось подібне: constexpr const char str [] = "Привіт, світ!";

Ні, не правильно. Це компілюється з clang та gcc. Я сподіваюсь, що це стандарт c ++ 11, але я не є мовою.

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

Що б я дуже хотів для c ++ 17, було б таке, що було б рівнозначним (для повного підходу №1)

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

Щось дуже схоже вже існує в стандарті для шаблонів, визначених користувачем, буквами, як також згадується void-pointer, але лише для цифр. До цього часу ще одна маленька хитрість - використовувати режим редагування заміщення + копіювати та вставляти

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

Якщо ви не заперечуєте проти макросу, це працює (трохи змінено з відповіді 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!)>();

2

Рішення kacey для створення унікального типу компіляції може, з незначними модифікаціями, також використовуватися з 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(); \
  }()

Використання:

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

test(CSTR("Hello"));

2

Граючи з картою boost hana, я натрапив на цю тему. Оскільки відповіді не вирішили мою проблему, я знайшов інше рішення, яке хочу додати тут, оскільки це може бути корисним для інших.

Моя проблема полягала в тому, що при використанні boost map hana з рядками hana компілятор все-таки генерував деякий код виконання (див. Нижче). Причина, очевидно, полягала в тому, що запитувати карту в час компіляції вона повинна constexpr. Це неможливо, оскільки BOOST_HANA_STRINGмакрос генерує лямбда, яку неможливо використовувати в constexprконтексті. З іншого боку, для карти потрібні рядки з різним вмістом, щоб вони були різними типами.

Оскільки рішення в цій темі або використовують лямбда, або не надають різні типи для різного вмісту, я вважав такий підхід корисним. Крім того, це дозволяє уникнути хакі str<'a', 'b', 'c'>синтаксису.

Основна ідея полягає у створенні версії Скотта Шурра, що шаблонується str_constна хеші персонажів. Це є c++14, але c++11має бути можливим при рекурсивному здійсненні crc32функції (див. Тут ).

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

Використання:

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

Отриманий код асемблера з clang-cl5.0 є:

012A1370  mov         eax,2  
012A1375  ret  

0

Я хотів би додати два дуже маленьких удосконалення до відповіді @ user1115339. Я згадав їх у коментарях до відповіді, але для зручності я поставлю тут рішення для копіювання пасти.

Єдина відмінність - FIXED_CSTRINGмакрос, який дозволяє використовувати рядки в шаблонах класів і в якості аргументів оператору індексу (корисно, якщо у вас є, наприклад, карта компіляції).

Живий приклад .

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")];
}

0

Моя власна реалізація заснована на підході з Boost.Hanaрядка (клас шаблону з різноманітними символами), але використовує лише C++11стандарт і constexprфункції з чітким перевіркою компіляційності (була б помилка часу компіляції, якщо не вираз часу компіляції). Може бути побудований із звичайної сирої C рядки замість фантазії {'a', 'b', 'c' }(через макрос).

Виконання: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/tackle/tmpl_string.hpp

Тести: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/src/tests/unit/test_tmpl_string.cpp

Приклади використання:

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.

Деталі про constexprчасовий кордон компіляції функцій: https://www.boost.org/doc/libs/1_65_0/libs/hana/doc/html/index.html#tutorial-appendix-constexpr

Інші деталі використання див. У тестах.

Весь проект на даний момент експериментальний.


0

У C ++ 17 з функцією помічника макрос легко створювати часові рядки компіляції:

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)>{} )

І це приклад використання:

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


static_assert(n == m);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.