Как создать целое число из строкового литерала во время компиляции?

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

Например, если все, что у нас есть, есть буквальный "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.

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

Ответы

Ответ 1

Очевидно, 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, в котором говорится, что "аргумент типа non-type типа" 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

(Отмена из другого моего ответа)

Если вы не возражаете изменить свое концептуальное определение "строкового литерала", например,
"425897" до '4258','97', вы можете использовать Boost. MPL 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;

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

Ответ 3

Может быть,?

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

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

Ответ 4

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

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

Как

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

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

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

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

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

Удачи!