2012-01-11 13 views
14

En C++, ¿es posible generar un número entero a partir de un literal de cadena utilizando solo recursos de tiempo de compilación?¿Cómo puedo generar un entero a partir de un literal de cadena en tiempo de compilación?

Por ejemplo, si todo lo que tenemos es el literal "6", ¿hay alguna forma de usarlo como un argumento de plantilla, como std::array<GET_INTEGER("6")> a;?

que sé sobre constexpr técnicas basadas, tales como:

template <int N> constexpr char get_char(const char s[N], int n) { 
    return s[n]; 
} 

Sin embargo constexpr aún no está listo en la mayoría de los compiladores, así que estoy en busca de soluciones utilizando probablemente macros y TMP.

Es solo para la experimentación, por lo que las ideas locas son bienvenidas.

+0

¿Qué significa "la mayoría de los compiladores"? Si "la mayoría de los compiladores" incluye lo que probablemente sean los compiladores más utilizados (gcc y Visual C++), entonces la respuesta es que no se puede hacer. –

+4

¿Por qué no solo escribe 6, sin las comillas? – littleadv

+0

Podría funcionar si utiliza los literales de caracteres 'GET_INTEGER ('6', '7', '8')', pero no creo que los literales de cadena funcionen. – kennytm

Respuesta

4

Al parecer gcc permite "abcd"[3] interpretarse como 'd', que permite que esto funciona (al menos en g ++ - 4.6 y 4.7):

#include <boost/preprocessor/repetition/enum.hpp> 

template <const char... characters> 
struct GetIntegerTemplate; 

template <const char head, const char... rest> 
struct GetIntegerTemplate<head, rest...> 
{ 
    typedef GetIntegerTemplate<rest...> Prev; 
    enum 
    { 
     power = Prev::power * 10, 
     value = (head - '0') * Prev::power + Prev::value 
    }; 
}; 

template <> 
struct GetIntegerTemplate<> 
{ 
    enum 
    { 
     power = 1, 
     value = 0 
    }; 
}; 

#define GET_NTH_CHARACTER(z, n, data) data[n] 
#define GET_INTEGER(length, the_string) GetIntegerTemplate<BOOST_PP_ENUM(length, GET_NTH_CHARACTER, the_string)>::value 

int main() 
{ 
    static_assert(GET_INTEGER(7, "1234567") == 1234567, "oops"); 
} 

Pero no se compilará el sonido metálico, que dice "no tipo argumento de la plantilla de tipo 'const char' no es una expresión constante integral ".


Lo que realmente hace es romper la cadena literal "1234567" en una lista de caracteres literales '1', '2', '3', '4', '5', '6', '7'. La instanciación

GetIntegerTemplate<'1', '2', '3', '4', '5', '6', '7'>::value 

es entonces invocado para convertir la lista en un número entero 1234567. La cadena → Char paso literal puede implicar comportamiento no estándar que puede no trabajar fuera de g ++ (es decir, peor que constexpr ☺), pero que GetIntegerTemplate<...>::value es portátil.

+0

¿Tal vez mediante conversión a 'int'? No tengo LLVM en este momento, pero podría valer la pena probarlo. – slaphappy

+0

@kbok: El problema no es el tipo, sino que '' 1234567 "[0]' en clang no se considera una constante. Esta es una extensión de gcc pero no puedo encontrar ninguna parte mencionando esto. – kennytm

+0

Ya veo, pensé que el problema era que no era 'integral'. A MSVC tampoco le gusta eso ("argumento de plantilla no válida para 'foo', expresión constante de tiempo de compilación esperada"); Es una pena que solo gcc lo soporte, mientras que es el único compilador que admite constexpr. – slaphappy

-1

¿Quizás?

template<int C> 
struct get_char 
{ 
    static const int value = C - 48; 
}; 

static_assert(get_char<'0'>::value == 0, ""); 
2

(Traspaso de another answer of mine)

Si no le importa cambiar su definición conceptual de 'cadena literal' de, por ejemplo,
"425897" a '4258','97', entonces puede usar Boost. MPL 's boost::mpl::string<> para lograr esto:

#include <cstddef> 
#include <boost/type_traits/is_integral.hpp> 
#include <boost/type_traits/is_same.hpp> 
#include <boost/type_traits/is_signed.hpp> 
#include <boost/mpl/and.hpp> 
#include <boost/mpl/assert.hpp> 
#include <boost/mpl/char.hpp> 
#include <boost/mpl/contains.hpp> 
#include <boost/mpl/end.hpp> 
#include <boost/mpl/eval_if.hpp> 
#include <boost/mpl/find_if.hpp> 
#include <boost/mpl/fold.hpp> 
#include <boost/mpl/front.hpp> 
#include <boost/mpl/identity.hpp> 
#include <boost/mpl/integral_c.hpp> 
#include <boost/mpl/minus.hpp> 
#include <boost/mpl/negate.hpp> 
#include <boost/mpl/next.hpp> 
#include <boost/mpl/not.hpp> 
#include <boost/mpl/pair.hpp> 
#include <boost/mpl/placeholders.hpp> 
#include <boost/mpl/plus.hpp> 
#include <boost/mpl/pop_front.hpp> 
#include <boost/mpl/push_back.hpp> 
#include <boost/mpl/reverse_fold.hpp> 
#include <boost/mpl/size_t.hpp> 
#include <boost/mpl/string.hpp> 
#include <boost/mpl/times.hpp> 
#include <boost/mpl/vector.hpp> 

namespace details 
{ 
    namespace mpl = boost::mpl; 

    typedef mpl::vector10< 
     mpl::char_<'0'>, mpl::char_<'1'>, mpl::char_<'2'>, mpl::char_<'3'>, 
     mpl::char_<'4'>, mpl::char_<'5'>, mpl::char_<'6'>, mpl::char_<'7'>, 
     mpl::char_<'8'>, mpl::char_<'9'> 
    > valid_chars_t; 

    template<typename IntegralT, typename PowerT> 
    struct power_of_10; 

    template<typename IntegralT, std::size_t Power> 
    struct power_of_10<IntegralT, mpl::size_t<Power> > : mpl::times< 
     power_of_10<IntegralT, mpl::size_t<Power - 1u> >, 
     mpl::integral_c<IntegralT, 10> 
    > { }; 

    template<typename IntegralT> 
    struct power_of_10<IntegralT, mpl::size_t<1u> > 
     : mpl::integral_c<IntegralT, 10> 
    { }; 

    template<typename IntegralT> 
    struct power_of_10<IntegralT, mpl::size_t<0u> > 
     : mpl::integral_c<IntegralT, 1> 
    { }; 

    template<typename IntegralT, typename StringT> 
    struct is_negative : mpl::and_< 
     boost::is_signed<IntegralT>, 
     boost::is_same< 
      typename mpl::front<StringT>::type, 
      mpl::char_<'-'> 
     > 
    > { }; 

    template<typename IntegralT, typename StringT> 
    struct extract_actual_string : mpl::eval_if< 
     is_negative<IntegralT, StringT>, 
     mpl::pop_front<StringT>, 
     mpl::identity<StringT> 
    > { }; 

    template<typename ExtractedStringT> 
    struct check_valid_characters : boost::is_same< 
     typename mpl::find_if< 
      ExtractedStringT, 
      mpl::not_<mpl::contains<valid_chars_t, mpl::_> > 
     >::type, 
     typename mpl::end<ExtractedStringT>::type 
    > { }; 

    template<typename ExtractedStringT> 
    struct pair_digit_with_power : mpl::first< 
     typename mpl::reverse_fold< 
      ExtractedStringT, 
      mpl::pair<mpl::vector0<>, mpl::size_t<0> >, 
      mpl::pair< 
       mpl::push_back< 
        mpl::first<mpl::_1>, 
        mpl::pair<mpl::_2, mpl::second<mpl::_1> > 
       >, 
       mpl::next<mpl::second<mpl::_1> > 
      > 
     >::type 
    > { }; 

    template<typename IntegralT, typename ExtractedStringT> 
    struct accumulate_digits : mpl::fold< 
     typename pair_digit_with_power<ExtractedStringT>::type, 
     mpl::integral_c<IntegralT, 0>, 
     mpl::plus< 
      mpl::_1, 
      mpl::times< 
       mpl::minus<mpl::first<mpl::_2>, mpl::char_<'0'> >, 
       power_of_10<IntegralT, mpl::second<mpl::_2> > 
      > 
     > 
    > { }; 

    template<typename IntegralT, typename StringT> 
    class string_to_integral_impl 
    { 
     BOOST_MPL_ASSERT((boost::is_integral<IntegralT>)); 

     typedef typename extract_actual_string< 
      IntegralT, 
      StringT 
     >::type ExtractedStringT; 
     BOOST_MPL_ASSERT((check_valid_characters<ExtractedStringT>)); 

     typedef typename accumulate_digits< 
      IntegralT, 
      ExtractedStringT 
     >::type ValueT; 

    public: 
     typedef typename mpl::eval_if< 
      is_negative<IntegralT, StringT>, 
      mpl::negate<ValueT>, 
      mpl::identity<ValueT> 
     >::type type; 
    }; 
} 

template<typename IntegralT, typename StringT> 
struct string_to_integral2 
    : details::string_to_integral_impl<IntegralT, StringT>::type 
{ }; 

template<typename IntegralT, int C0, int C1 = 0, int C2 = 0, 
    int C3 = 0, int C4 = 0, int C5 = 0, int C6 = 0, int C7 = 0> 
struct string_to_integral : string_to_integral2< 
    IntegralT, 
    boost::mpl::string<C0, C1, C2, C3, C4, C5, C6, C7> 
> { }; 

uso se vería así:

int i = string_to_integral<int, '4258','97'>::value; 
// or 
typedef boost::mpl::string<'4258','97'> str_t; 
unsigned j = string_to_integral2<unsigned, str_t>::value; 

se implementa soporte para números negativos, el apoyo para la detección de desbordamiento no es (pero su compilador probablemente dará una advertencia) .

+0

Eso es interesante, pero ¿qué es '4258''? – slaphappy

+0

@kbok: ''4258', '97'' es la forma de representar la cadena literal' "425897" 'de una manera que se puede usar con' boost :: mpl :: string <> '. – ildjarn

+0

@ildjarn: ¿Todos los impulsores incluidos son necesarios para ese código de muestra? Creo que recuerdo por qué odiaba usar boost ... –

-1

No estoy seguro de si es posible, pero eso es algo que podría intentar.

Puede disminuir el valor de un dígito de carácter en '0' para obtener ese valor en numéricos.

igual:

char a = '5'; 
int num = a - '0'; 

que resolvería el problema de un dígito.

Para que pueda resolver un número con muchos dígitos (como "12345"), deberá repetir todos los dígitos y sumar los resultados (cada uno multiplicado por 10^pos).

Eso sería fácil de hacer en tiempo de ejecución, pero en tiempo de compilación no es tan simple.

"recursión de tiempo de compilación" puede ser su amigo aquí. Para ser sincero, no podía pensar en ninguna solución para usarlo, pero podría encontrar uno.

¡Buena suerte!

Cuestiones relacionadas