Declarando convenientemente seqüências de tempo de compilação em C ++


137

Ser capaz de criar e manipular seqüências de caracteres durante o tempo de compilação em C ++ tem vários aplicativos úteis. Embora seja possível criar seqüências de tempo de compilação em C ++, o processo é muito complicado, pois a cadeia precisa ser declarada como uma sequência variável de caracteres, por exemplo,

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

Operações como concatenação de strings, extração de substring e muitas outras, podem ser facilmente implementadas como operações em seqüências de caracteres. É possível declarar seqüências de caracteres em tempo de compilação de forma mais conveniente? Caso contrário, existe uma proposta em andamento que permita uma declaração conveniente de seqüências de caracteres em tempo de compilação?

Por que as abordagens existentes falham

Idealmente, gostaríamos de poder declarar seqüências de caracteres em tempo de compilação da seguinte maneira:

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

ou, usando literais definidos pelo usuário,

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

onde decltype(str2)teria um constexprconstrutor. É possível implementar uma versão mais confusa da abordagem 1, aproveitando o fato de que você pode fazer o seguinte:

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

No entanto, a matriz precisaria ter ligação externa; portanto, para que a abordagem 1 funcione, teríamos que escrever algo como isto:

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

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

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

Escusado será dizer que isso é muito inconveniente. A abordagem 2 não é realmente possível de implementar. Se declarássemos um constexproperador literal ( ), como especificaríamos o tipo de retorno? Como precisamos que o operador retorne uma sequência variável de caracteres, precisamos usar o const char*parâmetro para especificar o tipo de retorno:

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

Isso resulta em um erro de compilação, porque snão é um constexpr. Tentar contornar isso fazendo o seguinte não ajuda muito.

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

O padrão determina que esse formulário literal específico do operador seja reservado para tipos inteiros e de ponto flutuante. Enquanto 123_siria funcionar, abc_snão iria. E se abandonarmos literais definidos pelo usuário por completo e usarmos uma constexprfunção regular ?

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

Como antes, encontramos o problema de que a matriz, agora um parâmetro para a constexprfunção, não é mais um constexprtipo.

Eu acredito que deve ser possível definir uma macro do pré-processador C que usa uma string e o tamanho da string como argumentos e retorna uma sequência que consiste nos caracteres da string (using BOOST_PP_FOR, stringification, subscritos de matriz e similares). No entanto, não tenho tempo (ou interesse suficiente) para implementar essa macro =)


2
O Boost possui uma macro que define uma string que pode ser usada como uma expressão constante. Bem, ele define uma classe que possui um membro de string. Você checou isso?
Pubby


1
O estouro de pilha não é o local apropriado para perguntar se existe uma proposta para algo. O melhor lugar para isso seria o site C ++ .
Nicol Bolas 07/04

1
Basicamente, você expande os caracteres armazenados no array / ptr em um pacote de parâmetros (como o Xeo fez). Embora eles não sejam divididos em argumentos de modelo que não sejam do tipo, você pode usá-los dentro de constexprfunções e inicializar matrizes (portanto, concat, substr etc.).
Dpr

1
@MareInfinitus Em resumo, as constexprseqüências de caracteres podem ser analisadas durante o tempo de compilação, para que você possa seguir caminhos de código diferentes, dependendo dos resultados. Essencialmente, você pode criar EDLs no C ++; as aplicações são bastante ilimitadas.
vazio-pointer

Respostas:


127

Não vi nada que correspondesse à elegância de Scott Schurrstr_const apresentada no C ++ Now 2012 . Requer, no constexprentanto.

Veja como você pode usá-lo e o que ele pode fazer:

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

Não fica muito mais legal do que a verificação do intervalo de tempo de compilação!

Tanto o uso quanto a implementação estão livres de macros. E não há limite artificial para o tamanho da string. Eu postaria a implementação aqui, mas estou respeitando os direitos autorais implícitos de Scott. A implementação está em um único slide de sua apresentação, link acima.


3
As operações que criam novas strings constexpr (como concatenação de strings e extração de substring) funcionam com essa abordagem? Talvez usando duas classes constexpr-string (uma baseada em str_conste a outra baseada em sequence), isso seja possível. O usuário usaria str_constpara inicializar a cadeia, mas operações subseqüentes que criam novas cadeias retornariam sequenceobjetos.
vazio-ponteiro

5
Este é um bom pedaço de código. No entanto, essa abordagem ainda possui uma falha em comparação com uma string declarada com uma sequência de caracteres como parâmetros do modelo: um str_const é um valor constante, e não um tipo, impedindo o uso de muitos idiomas metaprogramadores.
Jean-Bernard Jansen

1
@JBJansen, é possível, sem funções de hash, compilar uma string para um tipo que pode ser usado como parâmetro de modelo. Cada sequência diferente fornece um tipo diferente. A idéia básica é transformar a string em um pacote de caracteres template<char... cs>. Em teoria, você pode construir algo que pega uma string literal e compila o conteúdo de uma função. Veja a resposta por dyp. Uma biblioteca de aparência muito completa é metaparse . Essencialmente, você pode definir qualquer mapeamento de cadeias literais para tipos e implementá-lo com esse tipo de tecnologia.
Aaron McDaid

1
Não compartilho o entusiasmo ... não funciona com metafunções de modelo - muito irritante por causa do comprometimento tolo de que funções constexpr devem ser chamadas em tempo de execução - sem concatenação verdadeira, requer definição de uma matriz de caracteres (feia no cabeçalho) - embora isso é verdade para a maioria das soluções sem macros, graças ao comprometimento mencionado acima do constexpr - e a verificação de faixa não me impressiona muito, porque mesmo o baixo const const * * tem isso. Eu rolei minha própria sequência de pacotes de parâmetros, que também pode ser feita a partir de um literal (usando uma metafunção) ao custo de uma definição de matriz.
Arne Vogel

2
@ user975326: Acabei de revisar minha implementação e parece que adicionei um constexpr operator==. Desculpe. A apresentação de Scott deve começar sobre como fazer isso. É muito mais fácil no C ++ 14 do que no C ++ 11. Eu nem me incomodaria em tentar em C ++ 11. Veja mais recentes de Scott constexprfala aqui: youtube.com/user/CppCon
Howard Hinnant

41

Eu acredito que deveria ser possível definir uma macro de pré-processador C que usa uma string e o tamanho da string como argumentos e retorna uma sequência que consiste nos caracteres da string (usando BOOST_PP_FOR, stringification, subscritos de matriz e similares). No entanto, não tenho tempo (ou interesse suficiente) para implementar essa macro

é possível implementar isso sem depender do impulso, usando macro muito simples e alguns dos recursos do C ++ 11:

  1. lambdas variadic
  2. modelos
  3. expressões constantes generalizadas
  4. inicializadores de membros de dados não estáticos
  5. inicialização uniforme

(os dois últimos não são estritamente necessários aqui)

  1. precisamos ser capazes de instanciar um modelo variadico com indicações fornecidas pelo usuário de 0 a N - uma ferramenta também útil, por exemplo, para expandir a tupla no argumento da função de modelo variável (consulte as perguntas: Como faço para expandir uma tupla nos argumentos da função de modelo variável?
    " descompactar "uma tupla para chamar um ponteiro de função correspondente )

    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. defina um modelo variável chamado string com o parâmetro não-tipo 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. agora a parte mais interessante - para passar literais de caracteres no modelo de string:

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

uma demonstração simples de concatenação mostra o uso:

    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
Isso é tão simples que ainda não consigo acreditar que funcione. +1! Uma coisa: você não deve usar size_t em vez de não assinar?
kirbyfan64sos

1
E que tal usar em operator+vez de operator*? (str_hello + str_world)
Remy Lebeau

Prefiro essa solução ao método str_const popular de Scott Schurr, pois esse método garante que os dados subjacentes sejam consexpr. O método de Schurr permite criar um str_const em tempo de execução com uma variável de pilha char []; Não posso retornar com segurança um str_const de uma função ou passá-lo para outro thread.
Glenn

O link está morto ... alguém pode repassá-lo? @Glenn?
einpoklum

Você deve adicionar um par extra de chaves ao redor do lambda na sua CSTRINGmacro. Caso contrário, você não poderá criar uma CSTRINGchamada interna para um []operador, pois o dobro [[está reservado para atributos.
Florestan

21

Edit: como Howard Hinnant (e eu um pouco no meu comentário ao OP) apontou, talvez você não precise de um tipo com cada caractere único da string como um argumento de modelo único. Se você precisar disso, há uma solução sem macro abaixo.

Há um truque que encontrei ao tentar trabalhar com seqüências de caracteres em tempo de compilação. Ele requer a introdução de outro tipo além da "string de modelo", mas dentro das funções, você pode limitar o escopo desse tipo.

Ele não usa macros, mas alguns recursos do 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
Acabei de passar o fim de semana desenvolvendo de forma independente um trecho de código semelhante e criando um sistema muito básico para analisar seqüências de caracteres de tipo, por exemplo pair<int,pair<char,double>>. Fiquei orgulhoso de mim mesmo e depois descobri essa resposta, e a biblioteca de metaparse hoje! Eu realmente deveria pesquisar SO mais detalhadamente antes de iniciar projetos tolos como este :-) Acho que, em teoria, um compilador totalmente em C ++ poderia ser construído com esse tipo de tecnologia. Qual a coisa mais louca que foi construída com isso?
Aaron McDaid

Eu não sei. Eu nunca realmente usei essas técnicas em um projeto do mundo real, por isso não segui a abordagem. Embora eu me lembre de uma pequena variação do truque do tipo local, que foi um pouco mais conveniente ... talvez uma estática local char[].
Dj 13/07

Você quer dizer em my_str.print();vez de str.print();?
Mike

Existe uma versão ligeiramente mais curta do C ++ 14?
Mike

É uma pena que você tem que fazer o provedor (pelo menos em C ++ 11) - Eu realmente gostaria de ser capaz de usar uma corda na mesma declaração: /
Alec Teal

10

Se você não quiser usar a solução Boost, poderá criar macros simples que farão algo semelhante:

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

O único problema é o tamanho fixo de 64 caracteres (mais zero adicional). Mas pode ser facilmente alterado dependendo de suas necessidades.


Eu gosto muito desta solução; é muito simples e faz o trabalho com elegância. É possível modificar a macro para que nada seja acrescentado sizeof(str) > i(em vez de anexar os 0,tokens extras )? É fácil definir uma trimmetafunção que fará isso após a macro já ter sido chamada, mas seria bom se a própria macro pudesse ser modificada.
vazio-ponteiro

É impossível porque o analisador não entende sizeof(str). É possível adicionar manualmente o tamanho da string, MACRO_GET_STR(6, "Hello")mas isso exige que as macros Boost funcionem, porque a escrita manual requer 100 vezes mais código (você precisa implementar coisas simples como 1+1).
Yankes

6

Eu acredito que deve ser possível definir uma macro de pré-processador C que usa uma string e o tamanho da string como argumentos e retorna uma sequência que consiste nos caracteres da string (usando BOOST_PP_FOR, stringification, subscritos de matriz e similares)

Há um artigo: Usando seqüências de caracteres em metaprogramas de modelo C ++ de Abel Sinkovics e Dave Abrahams.

Ele tem alguma melhoria em relação à sua ideia de usar macro + BOOST_PP_REPEAT - não requer a passagem de tamanho explícito para a macro. Em resumo, é baseado no limite superior fixo para tamanho de string e "proteção contra sobrecarga de string":

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

mais aumento condicional :: mpl :: push_back .


Alterei minha resposta aceita para a solução de Yankes, pois resolve esse problema específico e o faz de maneira elegante sem o uso de constexpr ou código complexo de pré-processador.

Se você aceitar zeros à direita, loop de macro manuscrito, repetição de 2x de seqüência de caracteres na macro expandida e não tiver o Boost - então eu concordo - é melhor. No entanto, com o Boost seriam apenas três linhas:

DEMONSTRAÇÃO AO VIVO

#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

Inicialmente, mudei a solução para a de Yankes, pois ele forneceu o primeiro exemplo de trabalho aqui. Neste ponto, há muitas boas idéias concorrentes. Foi um erro meu escolher uma resposta tão cedo. No momento, observarei essa pergunta como sem resposta e esperarei até que eu tenha tempo de experimentar as idéias que todos postaram aqui. Há uma série de informações úteis nas respostas pessoas têm dado aqui ...
vazio-pointer

Eu concordo - por exemplo, eu gosto do exemplo de Howard Hinnant.
precisa saber é o seguinte

5

Ninguém parece gostar da minha outra resposta: - <. Então, aqui vou mostrar como converter um str_const em um tipo real:

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

Compila com clang ++ -stdlib = libc ++ -std = c ++ 14 (cl 3.7)


Funciona bem, mas não para o msvc 2019, pois reclama que str.size () não está sendo constexpr. Pode ser corrigido adicionando um segundo usando dedução separada str.size (). Talvez isso tenha atrasado alguns votos positivos ;-)
Zacharias

4

Aqui está uma solução C ++ 14 sucinta para criar um std :: tuple <char ...> para cada sequência de caracteres em tempo de compilação passada.

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

E aqui está um para criar um tipo único de tempo de compilação, aparado na outra postagem de macro.

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

É realmente muito ruim que literais definidos pelo usuário ainda não possam ser usados ​​para isso.


Na verdade, eles podem usar uma extensão suportada pelo GCC / Clang, mas vou esperar que isso seja adicionado ao padrão antes de postá-lo como resposta.
void-pointer

3

Um colega me desafiou a concatenar seqüências de caracteres na memória em tempo de compilação. Inclui instanciação de strings individuais também em tempo de compilação. A lista completa de códigos está aqui:

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

Tem certeza de que é feito em tempo de compilação? Há uma discussão sobre isso há algum tempo e, para mim, o resultado não está claro.
Dpr

Correr objdump -t a.out |grep mynão encontra nada. Quando comecei a digitar esse código, continuei experimentando a remoção constexprdas funções e objdumpmostrei quando constexprfoi omitido. Estou 99,9% confiante de que isso acontece no momento da compilação.
Átila Neves

1
Se você observar a desmontagem ( -S), notará que o gcc (4.7.2) realmente resolve as constexprfunções em tempo de compilação. No entanto, as seqüências de caracteres não são montadas em tempo de compilação. Em vez disso, (se eu interpretar corretamente) para cada caractere dessas seqüências "montadas", há uma movboperação própria , que é sem dúvida a otimização que você estava procurando.
Dpr

2
Isso é verdade. Tentei novamente com o gcc 4.9 e ainda faz a mesma coisa. No entanto, ontem eu pensei em tentar um compilador diferente. Com o clang, os movimentos bytewise não estão lá. Com o gcc, -Os também se livra deles, mas -O3 faz a mesma coisa.
Átila Neves

2

com base na ideia de Howard Hinnant, você pode criar uma classe literal que adicionará dois literais.

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" 

de onde str_atvem?
mic_e

é algo assim:str_at<int I>(const char* a) { return a[i]; }
Yankes

2

Sua abordagem nº 1 é a correta.

No entanto, o array precisaria ter ligação externa; portanto, para que a abordagem 1 funcione, precisamos escrever algo como isto: constexpr const char str [] = "Olá, mundo!";

Não, não está correto. Isso compila com clang e gcc. Espero que seja padrão c ++ 11, mas eu não sou um linguista.

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

O que eu realmente adoraria no c ++ 17 seria o seguinte para ser equivalente (para concluir a abordagem nº 1)

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

Algo muito semelhante já existe no padrão para literais definidos pelo usuário modelados, como o void-pointer também menciona, mas apenas para dígitos. Até então, outro pequeno truque é usar o modo de edição de substituição + copiar e colar de

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

Se você não se importa com a macro, isso funciona (ligeiramente modificado da resposta de 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

A solução da kacey para criar um tipo único de tempo de compilação pode, com pequenas modificações, também ser usada com o 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(); \
  }()

Usar:

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

test(CSTR("Hello"));

2

Enquanto brincava com o mapa de impulso hana, me deparei com este tópico. Como nenhuma das respostas resolveu meu problema, encontrei uma solução diferente que desejo adicionar aqui, pois pode ser potencialmente útil para outras pessoas.

Meu problema era que, ao usar o mapa boost hana com seqüências hana, o compilador ainda gerava algum código de tempo de execução (veja abaixo). Obviamente, o motivo foi que, para consultar o mapa em tempo de compilação, deve ser constexpr. Isso não é possível, pois a BOOST_HANA_STRINGmacro gera um lambda, que não pode ser usado em constexprcontexto. Por outro lado, o mapa precisa de strings com conteúdo diferente para serem de tipos diferentes.

Como as soluções neste segmento estão usando um lambda ou não fornecendo tipos diferentes para conteúdos diferentes, achei a seguinte abordagem útil. Também evita a str<'a', 'b', 'c'>sintaxe hacky .

A idéia básica é ter uma versão do str_constmodelo de Scott Schurr no hash dos personagens. É c++14, mas c++11deve ser possível com uma implementação recursiva da crc32função (veja aqui ).

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

Uso:

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

O código do assembler resultante com clang-cl5.0 é:

012A1370  mov         eax,2  
012A1375  ret  

0

Gostaria de adicionar duas pequenas melhorias à resposta de @ user1115339. Mencionei-os nos comentários para a resposta, mas por conveniência, colocarei aqui uma solução para copiar e colar.

A única diferença é a FIXED_CSTRINGmacro, que permite usar as strings nos modelos de classe e como argumentos para o operador de índice (útil se você tiver, por exemplo, um mapa de compilação).

Exemplo ao vivo .

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

Minha própria implementação é baseada na abordagem da Boost.Hanastring (classe de modelo com caracteres variados), mas utiliza apenas o C++11padrão e as constexprfunções com verificação rigorosa da compilação (seria um erro de tempo de compilação, se não uma expressão de tempo de compilação). Pode ser construído a partir da sequência C bruta usual, em vez de sofisticada {'a', 'b', 'c' }(por meio de uma macro).

Implementação: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/tackle/tmpl_string.hpp

Testes: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/src/tests/unit/test_tmpl_string.cpp

Exemplos de uso:

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.

Os detalhes sobre um constexprlimite de tempo de compilação de funções: https://www.boost.org/doc/libs/1_65_0/libs/hana/doc/html/index.html#tutorial-appendix-constexpr

Para outros detalhes de uso, consulte os testes.

Todo o projeto atualmente é experimental.


0

No C ++ 17 com uma função de macro auxiliar, é fácil criar sequências de tempo de compilação:

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

E este é um exemplo de uso:

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


static_assert(n == m);
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.