Удобное объявление строк времени компиляции в C++


возможность создавать и манипулировать строки во время компиляции в 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 на самом деле невозможно реализовать. Если бы мы объявили a (constexpr) литеральный оператор, то как бы мы указали тип возвращаемого значения? Так как нам нужен оператор, чтобы вернуть a вариативная последовательность символов, поэтому нам нужно будет использовать 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 не будет. Что, если мы бросим определяемый пользователем литералы вообще, и просто использовать обычный

12 123

12 ответов:

Я не видел ничего, чтобы соответствовать элегантности Скотт Шурр!--1--> представил в C++ Теперь 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!
}

Он не становится намного круче, чем проверка диапазона времени компиляции!

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

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

Это можно реализовать, не полагаясь на boost, используя очень простой макрос и некоторые из В C++11 особенности:

  1. lambdas variadic
  2. шаблоны
  3. обобщенные константные выражения
  4. инициализаторы нестатических элементов данных
  5. инициализации

(последние два здесь строго не требуются)

  1. нам нужно иметь возможность создавать экземпляр вариационного шаблона с пользовательскими указателями от 0 до N-инструмент также полезен, например, для расширения кортежа в аргумент функции variadic template (см. вопросы:как я могу развернуть Кортеж в аргументы шаблона функции с переменным числом аргументов это?
    "распаковка" кортежа для вызова соответствующего указателя функции)

    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 с не-типом голец параметр:

    namespace  compile_time
    {
        template<char...  str>
        struct  string
        {
            static  constexpr  const char  chars[sizeof...(str)+1] = {str..., ''};
        };
    
        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

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

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

Он не использует макросы, а скорее некоторые функции C++11.

#include <iostream>

// helper function
constexpr unsigned c_strlen( char const* str, unsigned count = 0 )
{
    return ('' == 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();
}

Если вы не хотите использовать импульс решению вы можете создать простой макрос, который будет делать то же самое:

#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 символов (плюс дополнительный ноль). Но это может быть легко изменено в зависимости от ваших потребностей.

Я считаю, что можно определить макрос препроцессора C, который принимает строку и размер строки в качестве аргументов и возвращает последовательность, состоящую из символов в строке (используя BOOST_PP_FOR, stringification, Array subscripts и т. п.)

есть статья: использование строк в метапрограммах шаблонов C++ Абель Синкович и Дэйв Абрахамс.

Он имеет некоторое улучшение по сравнению с вашей идеей использования макроса + BOOST_PP_REPEAT - это не требует передачи явного размера макроса. Короче говоря, он основан на фиксированном верхнем пределе для размера строки и "защита от переполнения строки":

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

плюс условный boost:: mpl:: push_back.


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

Если вы принимаете конечные нули, рукописный цикл макросов,2x повторение строки в расширенном макросе, и не имеют повышения - тогда я согласен - это лучше. Хотя, с повышением это будет всего три строки:

ДЕМО

#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

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

//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', '') -> 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', '');
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 '' 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] == '' ? 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";
}

на основе идеи от Говард Объектов вы можете создать литеральный класс, который будет добавлять два литерала вместе.

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" 

вот краткое решение C++14 для создания std:: tuple для каждой переданной строки времени компиляции.

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

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

никто, кажется, не нравится мой другой ответ :-<.>

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

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

моя проблема заключалась в том, что при использовании карты boost hana со строками hana компилятор все еще генерировал некоторый код времени выполнения (см. ниже). Причина была очевидна, что для запроса карты во время компиляции она должна быть constexpr. Это невозможно как BOOST_HANA_STRING макро генерирует лямбду, которая не может быть использована в constexpr контексте. С другой стороны, карта нуждается в строках с различным содержанием, чтобы быть разными типами.

поскольку решения в этом потоке либо используют лямбду, либо не предоставляют разные типы для разных содержаний, я нашел следующий подход полезным. Также это позволяет избежать суховато str<'a', 'b', 'c'> синтаксис.

основная идея состоит в том, чтобы иметь версию Скотта Шурра str_const шаблон на хэш символов. Это c++14, а c++11 должно быть возможно с рекурсивной реализацией

ваш подход #1 является правильным.

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

нет, не правильно. Это компилируется с clang и gcc. Я надеюсь, что его стандартный c++11, но я не язык laywer.

#include <iostream>

template <char... letters>
struct string_t{
    static char const * c_str() {
        static constexpr char string[]={letters...,''};
        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, было бы эквивалентно (to комплексный подход #1)

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

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

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

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

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

решение Кейси для создания уникального типа времени компиляции может, с небольшими изменениями, также использоваться с 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',''>
}

test(CSTR("Hello"));