Cómo declarar convenientemente cadenas de tiempo de compilación en C ++

137

Ser capaz de crear y manipular cadenas durante el tiempo de compilación en C ++ tiene varias aplicaciones útiles. Aunque es posible crear cadenas de tiempo de compilación en C ++, el proceso es muy engorroso, ya que la cadena debe declararse como una secuencia de caracteres variada, p. Ej.

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

Las operaciones como la concatenación de cadenas, la extracción de subcadenas y muchas otras se pueden implementar fácilmente como operaciones en secuencias de caracteres. ¿Es posible declarar cadenas en tiempo de compilación más convenientemente? Si no, ¿hay una propuesta en los trabajos que permitiría una conveniente declaración de cadenas en tiempo de compilación?

Por qué fallan los enfoques existentes

Idealmente, nos gustaría poder declarar cadenas de tiempo de compilación de la siguiente manera:

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

o, usando literales definidos por el usuario,

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

donde decltype(str2)tendria un constexprconstructor. Es posible implementar una versión más desordenada del enfoque 1, aprovechando el hecho de que puede hacer lo siguiente:

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

Sin embargo, la matriz necesitaría tener un enlace externo, por lo que para que el enfoque 1 funcione, tendríamos que escribir algo como esto:

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

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

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

No hace falta decir que esto es muy inconveniente. Enfoque 2 en realidad no es posible implementar. Si declaramos un constexproperador literal ( ), ¿cómo especificaríamos el tipo de retorno? Como necesitamos que el operador devuelva una secuencia de caracteres variada, deberíamos usar el const char*parámetro para especificar el tipo de retorno:

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

Esto da como resultado un error de compilación, porque sno es un constexpr. Intentar solucionar esto haciendo lo siguiente no ayuda mucho.

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

El estándar dicta que este formulario de operador literal específico está reservado para los tipos enteros y de coma flotante. Mientras 123_sfuncionaría, abc_sno lo haría. ¿Qué pasa si abandonamos por completo los literales definidos por el usuario y solo usamos una constexprfunción regular ?

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

Como antes, nos encontramos con el problema de que la matriz, ahora un parámetro para la constexprfunción, ya no es un constexprtipo.

Creo que debería ser posible definir una macro de preprocesador en C que tome una cadena y el tamaño de la cadena como argumentos, y devuelva una secuencia que consta de los caracteres en la cadena (usando BOOST_PP_FOR, stringificación, subíndices de matriz y similares). Sin embargo, no tengo el tiempo (o suficiente interés) para implementar tal macro =)

puntero vacío
fuente
2
Boost tiene una macro que define una cadena que se puede usar como una expresión constante. Bueno, define una clase que tiene un miembro de cadena. ¿Lo revisaste?
Pubby
1
Stack Overflow no es el lugar apropiado para preguntar si existe una propuesta para algo. El mejor lugar para esto sería el sitio C ++ .
Nicol Bolas
1
Básicamente, expande los caracteres almacenados en la matriz / ptr en un paquete de parámetros (como lo hizo Xeo). Aunque no están divididos en argumentos de plantilla que no sean de tipo, puede usarlos dentro de constexprfunciones e inicializar matrices (por lo tanto, concat, substr, etc.).
dyp
1
@MareInfinitus En resumen, las constexprcadenas se pueden analizar durante el tiempo de compilación, de modo que puede tomar diferentes rutas de código según los resultados. Esencialmente, puede crear EDL dentro de C ++; Las aplicaciones son bastante ilimitadas.
void-pointer el

Respuestas:

127

No he visto nada que coincida con la elegancia de Scott Schurrstr_const presentado en C ++ Now 2012 . Sin embargo, requiere constexpr.

Aquí le mostramos cómo puede usarlo y qué puede hacer:

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

¡No se vuelve mucho más genial que la comprobación del rango de tiempo de compilación!

Tanto el uso como la implementación están libres de macros. Y no hay límite artificial en el tamaño de la cadena. Publicaría la implementación aquí, pero estoy respetando los derechos de autor implícitos de Scott. La implementación está en una sola diapositiva de su presentación vinculada a lo anterior.

Howard Hinnant
fuente
3
¿Pueden funcionar las operaciones que crean nuevas cadenas constexpr (como la concatenación de cadenas y la extracción de subcadenas) con este enfoque? Tal vez usando dos clases constexpr-string (una basada en str_consty la otra basada en sequence), esto puede ser posible. El usuario usaría str_constpara inicializar la cadena, pero las operaciones posteriores que crean nuevas cadenas devolverían sequenceobjetos.
void-pointer el
55
Este es un buen código. Sin embargo, este enfoque todavía tiene una falla en comparación con una cadena declarada con una secuencia de caracteres como parámetros de plantilla: un str_const es un valor constante y no un tipo, lo que impide el uso de muchos modismos de metaprogramación.
Jean-Bernard Jansen
1
@JBJansen, es posible, sin funciones hash, compilar una cadena a un tipo que luego pueda usarse como parámetro de plantilla. Cada cadena diferente da un tipo diferente. La idea básica es convertir la cadena en un paquete de caracteres template<char... cs>. En teoría, podría construir algo que tome una cadena literal y compile el contenido en una función. Ver la respuesta por dyp. Una biblioteca de aspecto muy completo es metaparse . Esencialmente, puede definir cualquier asignación de cadenas literales a tipos e implementarla con este tipo de tecnología.
Aaron McDaid
1
No comparto el entusiasmo ... no funciona con metafunciones de plantilla, muy molesto debido al compromiso tonto de que las funciones constexpr se puedan invocar en tiempo de ejecución; no hay concatenación verdadera, requiere la definición de una matriz de caracteres (fea en el encabezado), aunque esto es cierto para la mayoría de las soluciones sin macro gracias al compromiso constexpr mencionado anteriormente, y la comprobación de rango no me impresiona mucho porque incluso el bajo constexpr const char * lo tiene. Hice rodar mi propia cadena de paquete de parámetros, que también se puede hacer de un literal (usando una metafunción) a costa de una definición de matriz.
Arne Vogel
2
@ user975326: Acabo de revisar mi implementación de esto y parece que agregué un constexpr operator==. Lo siento. La presentación de Scott debería ayudarlo a comenzar a hacerlo. Es mucho más fácil en C ++ 14 que en C ++ 11. Ni siquiera me molestaría en probar en C ++ 11. Vea las últimas constexprcharlas de Scott aquí: youtube.com/user/CppCon
Howard Hinnant
41

Creo que debería ser posible definir una macro de preprocesador C que tome una cadena y el tamaño de la cadena como argumentos, y devuelva una secuencia que consista en los caracteres de la cadena (usando BOOST_PP_FOR, stringificación, subíndices de matriz y similares). Sin embargo, no tengo el tiempo (o suficiente interés) para implementar tal macro

es posible implementar esto sin depender de boost, usando macro muy simple y algunas de las características de C ++ 11:

  1. lambdas variadic
  2. plantillas
  3. expresiones constantes generalizadas
  4. inicializadores de miembros de datos no estáticos
  5. inicialización uniforme

(los dos últimos no son estrictamente necesarios aquí)

  1. debemos ser capaces de crear una instancia de una plantilla variadic con indicadores proporcionados por el usuario de 0 a N, una herramienta también útil, por ejemplo, para expandir la tupla en el argumento de la función de plantilla variable (ver preguntas: ¿Cómo puedo expandir una tupla en los argumentos de la función de plantilla variable?
    " desempaquetar "una tupla para llamar a un puntero de función coincidente )

    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. luego defina una plantilla variadic llamada cadena con parámetro de tipo no 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. ahora la parte más interesante: pasar literales de caracteres a la plantilla de cadena:

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

Una simple demostración de concatenación muestra el 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

usuario1115339
fuente
1
Esto es tan simple que todavía no puedo creer que funcione. +1! Una cosa: ¿no deberías usar size_t en lugar de unsigned?
kirbyfan64sos
1
¿Y qué hay de usar en operator+lugar de operator*? (str_hello + str_world)
Remy Lebeau
Prefiero esta solución sobre el método str_const de Scott Schurr, ya que este método garantiza que los datos subyacentes sean constexpr. El método de Schurr me permite crear un str_const en tiempo de ejecución con una variable de pila char []; No puedo devolver con seguridad un str_const de una función o pasarlo a otro hilo.
Glenn
El enlace está muerto ... ¿alguien puede volver a publicarlo? @Glenn?
einpoklum
Debe agregar un par adicional de llaves alrededor de la lambda en su CSTRINGmacro. De lo contrario, no puede crear una CSTRINGllamada interna a un []operador, ya que el doble [[está reservado para los atributos.
Florestan
21

Editar: como señaló Howard Hinnant (y yo en mi comentario al OP), es posible que no necesite un tipo con cada carácter de la cadena como un argumento de plantilla única. Si necesita esto, hay una solución libre de macros a continuación.

Hay un truco que encontré al intentar trabajar con cadenas en tiempo de compilación. Requiere introducir otro tipo además de la "cadena de plantilla", pero dentro de las funciones, puede limitar el alcance de este tipo.

No utiliza macros, sino algunas características de 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
fuente
1
Acabo de pasar el fin de semana desarrollando independientemente un código similar y creando un sistema muy básico para analizar cadenas de texto, por ejemplo pair<int,pair<char,double>>. ¡Estaba orgulloso de mí mismo y descubrí esta respuesta y la biblioteca de metaparse hoy! Realmente debería buscar SO más a fondo antes de comenzar proyectos tontos como este :-) Supongo que, en teoría, un compilador completamente C ++ podría construirse a partir de este tipo de tecnología. ¿Qué es lo más loco que se ha construido con esto?
Aaron McDaid
No lo sé. Nunca he usado realmente estas técnicas en un proyecto del mundo real, por lo que no seguí el enfoque. Aunque creo recordar una ligera variación del truco de tipo local que era un poco más conveniente ... tal vez una estática local char[].
dyp
¿Quieres decir en my_str.print();lugar de str.print();?
Mike
¿Existe una versión C ++ 14 ligeramente más corta?
mike
Es una pena que tengas que hacer el proveedor (al menos en C ++ 11). Realmente me gustaría poder usar una cadena en la misma declaración: /
Alec Teal
10

Si no desea utilizar la solución Boost, puede crear macros simples que harán algo similar:

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

El único problema es el tamaño fijo de 64 caracteres (más cero adicional). Pero se puede cambiar fácilmente según sus necesidades.

Yankees
fuente
Me gusta mucho esta solución; Es muy simple y hace el trabajo con elegancia. ¿Es posible modificar la macro para que no se agregue nada sizeof(str) > i(en lugar de agregar los 0,tokens adicionales )? Es fácil definir una trimmetafunción que haga esto después de que la macro ya haya sido llamada, pero sería bueno si la macro en sí misma pudiera modificarse.
void-pointer el
Es imposible porque el analizador no lo entiende sizeof(str). Es posible agregar manualmente el tamaño de cadena, MACRO_GET_STR(6, "Hello")pero esto requiere que las macros Boost funcionen porque escribirlo manualmente requiere 100 veces más código (necesita implementos simples 1+1).
Yankes
6

Creo que debería ser posible definir una macro de preprocesador C que tome una cadena y el tamaño de la cadena como argumentos, y devuelva una secuencia que consista en los caracteres de la cadena (usando BOOST_PP_FOR, stringificación, subíndices de matriz y similares)

Hay un artículo: Uso de cadenas en metaprogramas de plantilla C ++ por Abel Sinkovics y Dave Abrahams.

Tiene alguna mejora sobre su idea de usar macro + BOOST_PP_REPEAT : no requiere pasar tamaño explícito a macro. En resumen, se basa en el límite superior fijo para el tamaño de la cadena y la "protección contra el exceso de cadenas":

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

más impulso condicional :: mpl :: push_back .


Cambié mi respuesta aceptada a la solución de Yankes, ya que resuelve este problema específico y lo hace de manera elegante sin el uso de constexpr o código de preprocesador complejo.

Si acepta ceros finales, bucles de macro escritos a mano, 2x repetición de cadena en macro expandida y no tiene Boost, entonces estoy de acuerdo, es mejor. Sin embargo, con Boost serían solo tres líneas:

DEMO EN 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
Evgeny Panasyuk
fuente
Inicialmente cambié la solución a Yankes ', ya que él proporcionó el primer ejemplo de trabajo aquí. En este punto, hay muchas buenas ideas competitivas. Fue mi error elegir una respuesta tan temprano. Actualmente comentaré esta pregunta como sin respuesta, y esperaré hasta que tenga tiempo de probar las ideas que todos han publicado aquí. Hay mucha información útil en las respuestas que la gente ha dado aquí ...
void-pointer
Estoy de acuerdo, por ejemplo, me gusta el ejemplo de Howard Hinnant.
Evgeny Panasyuk
5

A nadie parece gustarle mi otra respuesta: - <. Entonces, aquí muestro cómo convertir un str_const a un 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 con clang ++ -stdlib = libc ++ -std = c ++ 14 (clang 3.7)

Hombre agradable
fuente
Funciona bien, pero no para msvc 2019, ya que se queja de que str.size () no es constexpr. Se puede arreglar agregando un segundo usando deduciendo separadamente str.size (). Tal vez eso retrasó algunos votos positivos ;-)
Zacharias
4

Aquí hay una solución sucinta de C ++ 14 para crear std :: tuple <char ...> para cada cadena de tiempo de compilación que se pasa.

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

Y aquí hay uno para crear un tipo de tiempo de compilación único, recortado de la otra publicación 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");

Es realmente una lástima que los literales definidos por el usuario no se puedan usar para esto todavía.

kacey
fuente
En realidad, pueden usar una extensión compatible con GCC / Clang, pero voy a esperar antes de que esto se agregue al estándar antes de publicarlo como respuesta.
void-pointer
3

Un colega me retó a concatenar cadenas en la memoria en tiempo de compilación. Incluye la creación de instancias de cadenas individuales en tiempo de compilación también. El listado completo de códigos está aquí:

//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";
}
Átila Neves
fuente
¿Estás seguro de que se hace en tiempo de compilación? Hubo una discusión sobre esto hace algún tiempo, y para mí, el resultado no está claro.
dyp
Correr objdump -t a.out |grep myno encuentra nada. Cuando comencé a escribir este código, seguí experimentando con la eliminación constexprde las funciones y objdumples mostré cuándo constexprse omitió. Estoy 99.9% seguro de que sucede en tiempo de compilación.
Átila Neves
1
Si observa el desensamblaje ( -S), notará que gcc (4.7.2) resuelve las constexprfunciones en tiempo de compilación. Sin embargo, las cadenas no se ensamblan en tiempo de compilación. Por el contrario, (si lo interpreto correctamente) para cada carácter de esas cadenas "ensambladas", hay una movboperación propia , que posiblemente sea la optimización que estaba buscando.
dyp
2
Es verdad. Intenté nuevamente con gcc 4.9 y todavía hace lo mismo. Sin embargo, siempre pensé que este era el compilador que era estúpido. Solo ayer pensé en probar un compilador diferente. Con el sonido metálico, los mov bytewise no están allí en absoluto. Con gcc, -Os también se deshace de ellos, pero -O3 hace lo mismo.
Átila Neves
2

basado en la idea de Howard Hinnant , puede crear una clase literal que agregará dos literales juntos.

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" 
Yankees
fuente
de donde str_atviene
mic_e
es algo así:str_at<int I>(const char* a) { return a[i]; }
Yankees
2

Su enfoque # 1 es el correcto.

Sin embargo, la matriz necesitaría tener un enlace externo, por lo que para que el enfoque 1 funcione, tendríamos que escribir algo como esto: constexpr const char str [] = "¡Hola, mundo!";

No, no es correcto Esto compila con clang y gcc. Espero que sea c ++ 11 estándar, pero no soy un experto en lenguaje.

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

Lo que realmente me encantaría de c ++ 17 sería que lo siguiente fuera equivalente (para completar el enfoque n. ° 1)

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

Ya existe algo muy similar en el estándar para literales definidos por el usuario con plantilla, como también menciona void-pointer, pero solo para dígitos. Hasta entonces, otro pequeño truco es usar el modo de edición de anulación + copiar y pegar de

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

Si no te importa la macro, entonces esto funciona (ligeramente modificado de la respuesta 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!)>();
Hombre agradable
fuente
2

La solución de kacey para crear un tipo de tiempo de compilación único, con modificaciones menores, también se puede usar con 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(); \
  }()

Utilizar:

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

test(CSTR("Hello"));
smilingthax
fuente
2

Mientras jugaba con el mapa hana de impulso, me encontré con este hilo. Como ninguna de las respuestas resolvió mi problema, encontré una solución diferente que quiero agregar aquí, ya que podría ser potencialmente útil para otros.

Mi problema era que cuando usaba el mapa hana de impulso con cadenas hana, el compilador aún generaba un código de tiempo de ejecución (ver más abajo). Obviamente, la razón era que para consultar el mapa en tiempo de compilación debe ser constexpr. Esto no es posible ya que la BOOST_HANA_STRINGmacro genera una lambda, que no se puede usar enconstexpr contexto. Por otro lado, el mapa necesita cadenas con diferentes contenidos para ser de diferentes tipos.

Como las soluciones en este hilo están utilizando una lambda o no proporcionan diferentes tipos para diferentes contenidos, encontré útil el siguiente enfoque. También evita el hackystr<'a', 'b', 'c'> sintaxis .

La idea básica es tener una versión de Scott Schurr basada str_consten el hash de los personajes. Lo es c++14, pero c++11debería ser posible con una implementación recursiva de la crc32función (ver aquí ).

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

El código de ensamblador resultante con clang-cl5.0 es:

012A1370  mov         eax,2  
012A1375  ret  
Florestan
fuente
0

Me gustaría agregar dos mejoras muy pequeñas a la respuesta de @ user1115339. Los mencioné en los comentarios a la respuesta, pero por conveniencia pondré una solución de copiar y pegar aquí.

La única diferencia es la FIXED_CSTRINGmacro, que permite usar las cadenas dentro de las plantillas de clase y como argumentos para el operador de índice (útil si tiene, por ejemplo, un mapa de tiempo de compilación).

Ejemplo en 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")];
}
Florestan
fuente
0

Mi propia implementación se basa en el enfoque de la Boost.Hanacadena (clase de plantilla con caracteres variados), pero utiliza solo el C++11estándar y las constexprfunciones con un control estricto de tiempo de compilación (sería un error de tiempo de compilación si no es una expresión de tiempo de compilación). Se puede construir a partir de la cadena C sin procesar habitual en lugar de elegante {'a', 'b', 'c' }(a través de una macro).

Implementación: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/tackle/tmpl_string.hpp

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

Ejemplos 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.

Los detalles sobre un constexprlímite de tiempo de compilación de funciones: https://www.boost.org/doc/libs/1_65_0/libs/hana/doc/html/index.html#tutorial-appendix-constexpr

Para otros detalles de uso ver las pruebas.

Todo el proyecto actualmente es experimental.

Andry
fuente
0

En C ++ 17 con una función de macro auxiliar, es fácil crear cadenas de tiempo de compilación:

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

Y este es un ejemplo de uso:

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


static_assert(n == m);
zurrutik
fuente