resueltos - Convenientemente declarando cadenas de tiempo de compilación en C++
ejercicios resueltos de cadenas en c++ (12)
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 variadica de caracteres, por ejemplo
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 de tiempo de compilación más convenientemente? De lo contrario, ¿hay alguna propuesta en curso que permita una declaración conveniente de cadenas de tiempo de compilación?
Por qué los enfoques existentes fallan
Idealmente, nos gustaría poder declarar cadenas en 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)
tendría un constructor constexpr
. 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. El enfoque 2 en realidad no es posible de implementar. Si tuviéramos que declarar un operador literal ( constexpr
), ¿cómo podríamos especificar el tipo de devolución? Ya que necesitamos que el operador devuelva una secuencia variada de caracteres, entonces necesitaríamos usar el parámetro const char*
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 s
no es un constexpr
. Tratar de evitar 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 tipos enteros y de coma flotante. Mientras que 123_s
funcionarían, abc_s
no lo haría. ¿Qué pasa si abandonamos por completo los literales definidos por el usuario, y simplemente usamos una función constexpr
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 función constexpr
, ya no es un tipo constexpr
.
Creo que debería ser posible definir una macro de preprocesador C que toma una cadena y el tamaño de la cadena como argumentos, y devuelve una secuencia que consta de los caracteres en la cadena (usando BOOST_PP_FOR
, stringification, subíndices de matriz y similares). Sin embargo, no tengo el tiempo (o el interés suficiente) para implementar dicho macro =)
Creo que debería ser posible definir una macro de preprocesador C que toma una cadena y el tamaño de la cadena como argumentos, y devuelve una secuencia que consta de los caracteres en la cadena (usando BOOST_PP_FOR, stringification, subíndices de matriz y similares). Sin embargo, no tengo el tiempo (o el interés suficiente) 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:
- lambdas variadic
- plantillas
- expresiones constantes generalizadas
- Inicializadores de miembros de datos no estáticos
- Inicialización uniforme
(los dos últimos no son estrictamente necesarios aquí)
necesitamos poder instanciar una plantilla variadica con indicios suministrados por el usuario de 0 a N - una herramienta también útil por ejemplo para expandir tupla en el argumento de la función de plantilla variadic (ver preguntas: ¿Cómo puedo expandir una tupla en los argumentos de la función de la plantilla variadic?
"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; }; }
a continuación, defina una plantilla variadica llamada cadena con parámetro no de 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]; }
ahora la parte más interesante: pasar caracteres literales 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 demostración de concatenación simple 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;
}
Creo que debería ser posible definir una macro de preprocesador C que toma una cadena y el tamaño de la cadena como argumentos, y devuelve una secuencia que consta de los caracteres en la cadena (usando BOOST_PP_FOR, stringification, subíndices de matriz, etc.)
Hay un artículo: cpp-next.com/archive/2012/10/… por Abel Sinkovics y Dave Abrahams.
Tiene alguna mejora sobre su idea de usar macro + BOOST_PP_REPEAT - no requiere pasar el tamaño explícito a macro. En resumen, se basa en un límite superior fijo para el tamaño de cadena y "protección de desbordamiento de cadena":
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 elegantemente sin el uso de constexpr o código de preprocesador complejo.
Si acepta ceros al final, bucle de macros escrito a mano, 2 veces la repetición de la cadena en macro expandido, y no tiene Boost, entonces estoy de acuerdo, es mejor. Sin embargo, con Boost serían solo tres líneas:
#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
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)
Aquí hay una solución sucinta de C ++ 14 para crear un std :: tuple <char ...> para cada cadena de tiempo de compilación pasada.
#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 desde la otra macro publicación.
#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 una lástima que los literales definidos por el usuario no puedan usarse para esto todavía.
Editar: como señaló Howard Hinnant (y algo de mí en mi comentario al OP), es posible que no necesite un tipo con cada carácter de la cadena como argumento de una sola plantilla. Si necesita esto, hay una solución sin macro a continuación.
Hay un truco que encontré al intentar trabajar con cadenas de caracteres en el momento de la 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();
}
La solución de kacey para crear un tipo de lata única en tiempo de compilación, con pequeñas modificaciones, 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"));
Mientras jugaba con el mapa de hana boost, encontré 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 fue que al usar el mapa de hana boost con hana strings, el compilador aún generaba algún código de tiempo de ejecución (ver a continuación). La razón fue obviamente que para consultar el mapa en tiempo de compilación debe ser constexpr
. Esto no es posible ya que la macro BOOST_HANA_STRING
genera un lambda, que no se puede usar en contexto constexpr
. Por otro lado, el mapa necesita cadenas con diferentes contenidos para ser diferentes tipos.
Como las soluciones en este hilo están usando un lambda o no proporcionan diferentes tipos para diferentes contenidos, encontré útil el siguiente enfoque. También evita la sintaxis hacky str<''a'', ''b'', ''c''>
.
La idea básica es tener una versión de str_const
de Scott Schurr str_const
plantilla en el hash de los personajes. Es c++14
, pero c++11
debería ser posible con una implementación recursiva de la función crc32
(ver here ).
// 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://.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 ))>
Usage:
#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;
}
Resulting assembler code with clang-cl
5.0 is:
012A1370 mov eax,2
012A1375 ret
No he visto nada que coincida con la elegancia del str_const
de Scott Schurr presentado en C ++ Now 2012 . Sin embargo, requiere constexpr
.
A continuación, 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 pone mucho más frío que la verificación del rango en tiempo de compilación!
Tanto el uso como la implementación están libres de macros. Y no hay un límite artificial en el tamaño de la cuerda. 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 la anterior.
Si no quieres usar la solución de Boost puedes crear macro simple que hará 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 puede cambiarse fácilmente según sus necesidades.
Un colega me desafió a concatenar cadenas en memoria en tiempo de compilación. Incluye la instanciación de cadenas individuales en tiempo de compilación también. El listado completo del código 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";
}
basado en la idea de Howard Hinnant puedes 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"
Your approach #1 is the correct one.
However, the array would need to have external linkage, so to get approach 1 to work, we would have to write something like this: constexpr const char str[] = "Hello, world!";
No, not correct. This compiles with clang and gcc. I hope its standard c++11, but i am not a language laywer.
#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;
}
What I would really love for c++17 would be the following to be equivalent (to complete approach #1)
// for template <char...>
<"Text"> == <''T'',''e'',''x'',''t''>
Something very similar already exists in the standard for templated user defined literals,as void-pointer also mentions, but only for digits. Until then another little trick is to use the override editing mode + copy and paste of
string_t<'' '','' '','' '','' '','' '','' '','' '','' '','' '','' '','' '','' ''>;
Si no te importa el 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!)>();