Удобное объявление строк времени компиляции в 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 есть макрос, который определяет строку, которая может использоваться как постоянное выражение. Ну, он определяет класс, который имеет строковый член. Вы это проверили?
Pubby
6
Вы проверяли cpp-next.com/archive/2012/10/… ?
Евгений Панасюк
1
Stack Overflow - не подходящее место, чтобы спрашивать, существует ли какое-либо предложение. Лучшее место для этого - сайт 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
Могут ли операции, которые создают новые строки constexpr (например, конкатенация строк и извлечение подстроки) работать с этим подходом? Возможно, используя два класса constexpr-string (один на основе, str_constа другой на основе sequence), это может быть возможным. Пользователь будет использовать str_constдля инициализации строки, но последующие операции, создающие новые строки, будут возвращать sequenceобъекты.
void-pointer
5
Это хороший фрагмент кода. Однако у этого подхода все еще есть недостаток по сравнению со строкой, объявленной с последовательностью символов в качестве параметров шаблона: str_const является постоянным значением, а не типом, что предотвращает использование множества идиом метапрограммирования.
Жан-Бернар Янсен
1
@JBJansen, можно без хэш-функций скомпилировать строку в тип, который затем можно использовать в качестве параметра шаблона. Каждая строка соответствует разному типу. Основная идея - превратить строку в набор символов template<char... cs>. Теоретически вы можете создать что-то, что принимает буквальную строку и компилирует ее содержимое в функцию. См. Ответ dyp. Очень полная на вид библиотека метапарчатая . По сути, вы можете определить любое отображение литеральных строк в типы и реализовать его с помощью такой технологии.
Aaron McDaid
1
Я не разделяю энтузиазма ... не работает с метафункциями шаблонов - очень раздражает из-за глупого компромисса, заключающегося в том, что функции constexpr должны быть вызваны во время выполнения - нет истинной конкатенации, требуется определение массива символов (уродливое в заголовке) - хотя это верно для большинства решений без макросов благодаря вышеупомянутому компромиссу constexpr - и проверка диапазона не впечатляет меня, потому что даже скромный constexpr 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. затем определите вариативный шаблон с именем string с параметром, отличным от типа 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

user1115339
источник
1
Это настолько просто, что я до сих пор не могу поверить, что это работает. +1! Одно: не следует ли использовать size_t вместо unsigned?
kirbyfan64sos
1
А как насчет использования operator+вместо operator*? (str_hello + str_world)
Реми Лебо
Я предпочитаю это решение популярному методу Скотта Шурра str_const, поскольку этот метод гарантирует, что базовые данные - constexpr. Метод Шурра позволяет мне создавать str_const во время выполнения с переменной стека char []; Я не могу безопасно вернуть str_const из функции или передать его другому потоку.
Гленн,
Ссылка мертвая ... может кто перепостить? @ Гленн?
einpoklum
Вы должны добавить дополнительную пару скобок вокруг лямбды в свой CSTRINGмакрос. В противном случае вы не сможете создать CSTRINGвнутренний вызов []оператора, поскольку double [[зарезервированы для атрибутов.
Флорестан
21

Изменить: как указал Ховард Хиннант (и я несколько в моем комментарии к OP), вам может не понадобиться тип с каждым отдельным символом строки в качестве одного аргумента шаблона. Если вам это действительно нужно, ниже есть решение без макросов.

Есть уловка, которую я обнаружил, пытаясь работать со строками во время компиляции. Требуется ввести другой тип, помимо «строки шаблона», но внутри функций вы можете ограничить область действия этого типа.

Он не использует макросы, а использует некоторые функции 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();
}
DYP
источник
1
Я только что провел выходные независимо, разрабатывая аналогичный фрагмент кода и создавая очень простую систему для анализа строк типов, например pair<int,pair<char,double>>. Я гордился собой, а потом открыл для себя этот ответ и библиотеку метапарков сегодня! Я действительно должен поискать ТАК более тщательно, прежде чем начинать такие глупые проекты :-) Я полагаю, что теоретически можно построить полностью компилятор C ++ на основе такой технологии. Какая самая безумная вещь из всего этого была построена?
Aaron McDaid
Я не знаю. Я никогда не использовал эти методы в реальных проектах, поэтому я не придерживался этого подхода. Хотя я думаю, что помню небольшую вариацию трюка с локальным типом, который был немного удобнее ... может быть, локальная статика char[].
dyp
Вы имеете в виду my_str.print();вместо str.print();?
Майк
Есть ли немного более короткая версия C ++ 14?
Майк
Жалко, что вы должны сделать провайдера (по крайней мере, в C ++ 11) - я бы действительно хотел иметь возможность использовать строку в том же операторе: /
Алек Тил
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 символа (плюс дополнительный ноль). Но его можно легко изменить в зависимости от ваших потребностей.

Yankes
источник
Мне очень нравится это решение; это очень просто и элегантно выполняет свою работу. Можно ли изменить макрос так, чтобы ничего не добавлялось 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 .


Я изменил свой принятый ответ на решение Янкса, поскольку оно решает эту конкретную проблему и делает это элегантно без использования 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
Евгений Панасюк
источник
Сначала я изменил решение на решение Янкса, поскольку он представил здесь первый рабочий пример. На данный момент есть много хороших конкурирующих идей. Я ошибся, так рано выбрав ответ. В настоящее время я отмечу этот вопрос как оставшийся без ответа и отложу, пока у меня не будет времени опробовать идеи, которые все здесь разместили. В ответах, которые люди здесь дали, есть много полезной информации ...
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

Коллега предложил мне объединить строки в памяти во время компиляции. Он также включает создание экземпляров отдельных строк во время компиляции. Полный листинг кода находится здесь:

//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
1
Бег objdump -t a.out |grep myничего не находит. Когда я начал набирать этот код, я продолжал экспериментировать с удалением constexprиз функций и objdumpпоказывал их, когда они constexprбыли опущены. Я уверен на 99,9%, что это происходит во время компиляции.
Атила Невес
1
Если вы посмотрите на disassembly ( -S), вы заметите, что gcc (4.7.2) действительно разрешает constexprфункции во время компиляции. Тем не менее, строки не собираются во время компиляции. Скорее (если я правильно интерпретирую) для каждого символа этих «собранных» строк существует собственная movbоперация, которая, возможно, является оптимизацией, которую вы искали.
dyp
2
Это правда. Я попробовал еще раз с gcc 4.9, и он по-прежнему делает то же самое. Я всегда думал, что компилятор тупой. Только вчера я подумал попробовать другой компилятор. С clang побайтные movs вообще отсутствуют. С gcc, -Os тоже избавляется от них, но -O3 делает то же самое.
Атила Невес
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");

Очень жаль, что пользовательские литералы пока не могут быть использованы для этого.

Kacey
источник
Фактически, они могут использовать расширение, поддерживаемое GCC / Clang, но я собираюсь подождать, прежде чем оно будет добавлено в стандарт, прежде чем публиковать его в качестве ответа.
void-pointer
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" 
Yankes
источник
откуда str_atидет?
mic_e
это что-то вроде этого:str_at<int I>(const char* a) { return a[i]; }
Янкес
2

Ваш подход №1 правильный.

Однако у массива должна быть внешняя связь, поэтому для того, чтобы подход 1 работал, нам нужно было бы написать что-то вроде этого: constexpr const char str [] = "Hello, world!";

Нет, не правильно. Это компилируется с помощью clang и gcc. Я надеюсь, что это стандартный С ++ 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;
}

То, что мне действительно понравилось бы в С ++ 17, было бы следующим, чтобы быть эквивалентным (для завершения подхода №1)

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

Нечто подобное уже существует в стандарте для шаблонных пользовательских литералов, о чем также упоминается void-pointer, но только для цифр. А пока еще одна небольшая хитрость - использовать режим редактирования переопределения + копирование и вставка

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

Если вы не возражаете против макроса, это работает (немного изменено из ответа Янкса):

#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"));
smilingthax
источник
2

Играя с картой буст хана наткнулся на эту ветку. Поскольку ни один из ответов не решил мою проблему, я нашел другое решение, которое хочу добавить здесь, поскольку оно может быть потенциально полезно для других.

Моя проблема заключалась в том, что при использовании карты boost 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

Для других деталей использования смотрите тесты.

В настоящее время весь проект является экспериментальным.

Andry
источник
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);
zurrutik
источник