2012-01-11 18 views
14

W języku C++ możliwe jest generowanie liczby całkowitej z literału łańcuchowego przy użyciu jedynie narzędzi do kompilacji?Jak wygenerować liczbę całkowitą z literału ciągu w czasie kompilacji?

Na przykład, jeśli wszystko, co mamy, to literał "6", czy istnieje jakiś sposób użycia go jako argumentu szablonu, np. std::array<GET_INTEGER("6")> a;?

wiem o constexpr opartych technik, takich jak:

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

Jednak constexpr nie jest jeszcze gotowy na większości kompilatorów, więc szukam rozwiązań wykorzystujących prawdopodobnie makra i TMP.

Jest to po prostu eksperyment, więc szalone pomysły są mile widziane.

+0

Co oznacza "większość kompilatorów"? Jeśli "większość kompilatorów" zawiera prawdopodobnie najpopularniejsze kompilatory (gcc i Visual C++), to odpowiedź brzmi, że nie można tego zrobić. –

+4

Dlaczego po prostu nie pisać 6 bez cudzysłowów? – littleadv

+0

Może to działać, jeśli użyjesz literałów znaków 'GET_INTEGER ('6', '7', '8')', ale nie sądzę, że literały łańcuchowe będą działały. – kennytm

Odpowiedz

4

Widocznie gcc pozwala "abcd"[3] być interpretowane jako 'd', który pozwala na to, aby działać (przynajmniej na g ++ - 4.6 i 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"); 
} 

Ale to nie będzie kompilować na brzękiem, który mówi „nie- typ argumentu typu "const char" nie jest stałym wyrażeniem stałym ".


Co to naprawdę nie jest rozbić ciąg dosłownego "1234567" do listy literałów znakowych '1', '2', '3', '4', '5', '6', '7'. Konkretyzacja

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

zostaje wywołany, aby włączyć listę do liczby całkowitej 1234567. Ciąg → char dosłowne krok może wiązać niestandardowych zachowań, które nie mogą pracować poza g ++ (czyli gorzej niż constexpr ☺), ale że GetIntegerTemplate<...>::value jest przenośny.

+0

Może przez rzutowanie na 'int'? Nie mam teraz LLVM, ale to może być warte testowania. – slaphappy

+0

@kbok: Problem nie jest typem, ale '' 1234567 "[0]' w klangu nie jest uważany za stałą. To jest po prostu rozszerzenie gcc, ale nigdzie nie mogę o tym wspomnieć. – kennytm

+0

Rozumiem, myślałem, że problem polega na tym, że nie był on "integralną". MSVC też tego nie lubi ("niepoprawny argument szablonu dla" foo ", oczekiwane wyrażenie czasu kompilacji"); szkoda, że ​​obsługuje go tylko gcc, podczas gdy jest to jedyny kompilator, który obsługuje constexpr. – slaphappy

-1

Może?

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

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

(przeksięgowanie z another answer of mine)

Jeśli nie przeszkadza zmieniając koncepcyjnej definicji „łańcucha dosłowny” od np
do '4258','97', następnie można użyć Boost. MPL „s boost::mpl::string<> do osiągnięcia tego celu:

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

Wykorzystanie wyglądałby następująco:

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; 

Wsparcie dla liczb ujemnych jest realizowany, wsparcie dla wykrywania przepełnienia nie jest (ale kompilator będzie prawdopodobnie dać ostrzeżenie) .

+0

To interesujące, ale co to jest "4258"? – slaphappy

+0

@kbok: ''4258', '97'' jest sposobem reprezentowania ciągu literowego" 425897 "' w sposób, który można wykorzystać za pomocą 'boost :: mpl :: string <>'. – ildjarn

+0

@ildjarn: Czy wszystkie te boosty są potrzebne dla tego przykładowego kodu? Myślę, że pamiętam, dlaczego nienawidziłem używania boost ... –

-1

Nie jestem pewien, czy jest to możliwe, ale jest to coś, co można wypróbować.

Możesz zmniejszyć wartość cyfry znaku o "0", aby uzyskać tę wartość w liczbach.

odczuwalna:

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

To rozwiązałoby problem dla jednej cyfry.

Aby rozwiązać numer z wieloma cyframi (np. "12345"), należy zapętlić wszystkie cyfry i zsumować wyniki (każdy pomnożony przez 10^pos).

Byłoby to łatwe do zrobienia w czasie wykonywania, ale w czasie kompilacji nie jest to takie proste.

"rekurencja czasu kompilacji" może być tu twoim przyjacielem. Szczerze mówiąc, nie mogłem myśleć o jakimkolwiek rozwiązaniu, ale możesz go znaleźć.

Powodzenia!

Powiązane problemy