Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I generate an integer from a string literal at compile-time?

In C++, is it possible to generate an integer from a string literal using only compile-time facilities ?

For instance, if all we have is the literal "6", is there some way to use it as a template argument, like std::array<GET_INTEGER("6")> a; ?

I know about constexpr-based techniques, such as :

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

However constexpr isn't ready yet on most compilers, so I'm looking for solutions using probably macros and TMP.

It's just for experimentation, so crazy ideas are welcome.

like image 502
slaphappy Avatar asked Jan 11 '12 18:01

slaphappy


People also ask

Can you return a string literal?

Strings in C are arrays of char elements, so we can't really return a string - we must return a pointer to the first element of the string. All forms are perfectly valid. Note the use of const , because from the function I'm returning a string literal, a string defined in double quotes, which is a constant.

Is it possible to modify a string literal?

The behavior is undefined if a program attempts to modify any portion of a string literal. Modifying a string literal frequently results in an access violation because string literals are typically stored in read-only memory.

How do you use a string literal?

A "string literal" is a sequence of characters from the source character set enclosed in double quotation marks (" "). String literals are used to represent a sequence of characters which, taken together, form a null-terminated string. You must always prefix wide-string literals with the letter L.

Can numbers be string literals?

The Imagecraft and Metrowerks compilers recognize three types of literals (numeric, character, string). Numbers can be written in three bases (decimal, octal, and hexadecimal).


2 Answers

Apparently gcc allows "abcd"[3] be interpreted as 'd', which allows this to work (at least on g++-4.6 and 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");
}

But it won't compile on clang, which says "non-type template argument of type 'const char' is not an integral constant expression".


What it really does is to break down the string literal "1234567" into a list of character literals '1', '2', '3', '4', '5', '6', '7'. The instantiation

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

is then invoked to turn the list into an integer 1234567. The string → char literal step may involve non-standard behavior which may not work outside of g++ (i.e. worse than constexpr ☺), but that GetIntegerTemplate<...>::value is portable.

like image 122
kennytm Avatar answered Sep 29 '22 05:09

kennytm


(Reposting from another answer of mine)

If you don't mind changing your conceptual definition of 'string literal' from e.g.
"425897" to '4258','97', then you can use Boost.MPL's boost::mpl::string<> to accomplish this:

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

Usage would look like:

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;

Support for negative numbers is implemented, support for overflow detection is not (but your compiler will probably give a warning).

like image 33
ildjarn Avatar answered Sep 29 '22 07:09

ildjarn