Как мне сгенерировать целое число из строкового литерала во время компиляции? - PullRequest
14 голосов
/ 11 января 2012

В C ++ возможно ли сгенерировать целое число из строкового литерала, используя только средства времени компиляции?

Например, если все, что у нас есть, это литерал "6", есть ли какой-то способ использоватьэто как аргумент шаблона, например std::array<GET_INTEGER("6")> a;?

Я знаю о constexpr методах, таких как:

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

Однако constexpr еще не готов на большинствекомпиляторы, поэтому я ищу решения, использующие, вероятно, макросы и TMP.

Это просто для экспериментов, поэтому сумасшедшие идеи приветствуются.

Ответы [ 4 ]

4 голосов
/ 11 января 2012

Очевидно, что gcc позволяет интерпретировать "abcd"[3] как 'd', что позволяет работать (по крайней мере, на g ++ - 4.6 и 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");
}

Но он не скомпилируется в clang, который говорит, что «нетипичный аргумент шаблона типа« const char »не является целочисленным константным выражением».


Что он действительно делает, так это разбивает строковый литерал "1234567" на список символьных литералов '1', '2', '3', '4', '5', '6', '7'. Инстанциация

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

затем вызывается для превращения списка в целое число 1234567. Строковый буквенный шаг char может включать нестандартное поведение, которое может не работать вне g ++ (то есть хуже, чем constexpr ☺), но это GetIntegerTemplate<...>::value портативный.

2 голосов
/ 11 января 2012

(Репост от другой мой ответ )

Если вы не возражаете изменить свое концептуальное определение «строковый литерал», например, на
"425897" до '4258','97', тогда вы можете использовать Повышение . MPL 's boost::mpl::string<> для выполнения этого:

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

Использование будет выглядеть так:

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;

Реализована поддержка отрицательных чисел, нет поддержки обнаружения переполнения (но ваш компилятор, вероятно, выдаст предупреждение).

0 голосов
/ 11 января 2012

Я не уверен, возможно ли это, но вы можете попробовать это.

Вы можете уменьшить значение цифры на 0, чтобы получить это значение в цифрах.

Как:

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

Это решит вашу проблему за одну цифру.

Чтобы вы могли решить многозначное число (например, «12345»), вам нужно было бы зациклить всецифры и суммируют результаты (каждый умноженный на 10 ^ pos).

Это было бы легко сделать во время выполнения, но во время компиляции это не так просто.

"compileвремя рекурсии "может быть, ваш друг здесь.Если честно, я не мог придумать ни одного решения, использующего его, но вы могли бы найти его.

Удачи!

0 голосов
/ 11 января 2012

Может быть?

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

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