How do I convert a C string to a int at compile time?
Asked Answered
P

3

12

I want to be able to pass an integer or a double (or a string) as a template argument and in some instances convert the result to an integer and use it as a template argument for a type in the class.

Here's what I've tried:

template <typename MPLString>
class A
{
    // the following works fine
    int fun()
    {
      // this function should return the int in the boost mpl type passed to it
      // (e.g. it might be of the form "123")
      return std::stoi(boost::mpl::c_str<MPLString>::value);
    }

    // the following breaks because std::stoi is not constexpr
    std::array<int, std::stoi(boost::mpl::c_str<MPLString>::value)> arr;
};

Can I do this somehow? I've tried std::stoi and atoi but neither are constexpr... Any ideas how this could be done (I cannot change the template parameter to take an int directly, as it might be a double).

Pyroelectric answered 8/8, 2014 at 2:20 Comment(8)
Your title says double, your code says integer. Which one? One is harder than the other. Looking at the code, I'm not even sure how you expect this to work at all.Lewendal
I remember I have seen constexpr implementation of atoi somewhere is this siteSterilize
std::array has two template parameters, typename T and size_t N. Which one do you want? Because double will just truncate from size_t. Do you want to just detect if the string can be an int or double?Lewendal
In C++14, these constexpr functions become a lot more trivial. You could pretty much implement a naive atoi and mark it constexpr.Pilot
Take a look here: enki-tech.blogspot.ca/2012/09/… , the author defines (among other things), a constexpr atoi. I also remembered to have seen a similar question on SO, but cannot find it anymore. I found the compile-time itoa here though: https://mcmap.net/q/523680/-c-convert-integer-to-string-at-compile-time/3093378Mme
@Lewendal I don't want to detect anything, I just want to do the conversion from boost::mpl to int at compile time.Pyroelectric
This isn't too hard if you use regular C strings. Unfortunately, I don't know much about boost::mpl.Lewendal
@Lewendal well you're in luck because boost::mpl::c_str<MPLString>::value is a constexpr of type char*Pyroelectric
L
20

Defining a constexpr stoi isn't too hard with regular C strings. It can be defined as follows:

constexpr bool is_digit(char c) {
    return c <= '9' && c >= '0';
}

constexpr int stoi_impl(const char* str, int value = 0) {
    return *str ?
            is_digit(*str) ?
                stoi_impl(str + 1, (*str - '0') + value * 10)
                : throw "compile-time-error: not a digit"
            : value;
}

constexpr int stoi(const char* str) {
    return stoi_impl(str);
}

int main() {
    static_assert(stoi("10") == 10, "...");
}

The throw expression is invalid when used in constant expressions so it'll trigger a compile time error rather than actually throwing.

Lewendal answered 8/8, 2014 at 3:38 Comment(5)
How does invalid-throwing work? When compile that expression, doesn't compiler cause error even if is_digit(*str) is true?Melanite
@Melanite Only certain things are allowed in 'constant expressions'. If you use a constexpr function in a 'constant expression' context and one of the things shows up that aren't allowed, the compiler will fail to compile it.Lewendal
@Lewendal Oh, certainly we need a bit long template metaprogramming if that isn't exist >o<Melanite
Please don't use nested ternary operators.Waziristan
@Waziristan in C++11 this used to be the only way to do it, now with relaxed constexpr in C++14 and above the nested ternary operators are no longer necessary. However, the tags for this question remain C++11.Lewendal
V
2

mystoi():

#include <cstdint>     // for int32_t
#include <iosfwd>      // for ptrdiff_t, size_t
#include <iterator>    // for size
#include <stdexcept>   // for invalid_argument
#include <string_view> // for string_view

constexpr std::int32_t mystoi(std::string_view str, std::size_t* pos = nullptr) {
    using namespace std::literals;
    const auto numbers = "0123456789"sv;

    const auto begin = str.find_first_of(numbers);
    if (begin == std::string_view::npos)
        throw std::invalid_argument{"stoi"};

    const auto sign = begin != 0U && str[begin - 1U] == '-' ? -1 : 1;
    str.remove_prefix(begin);

    const auto end = str.find_first_not_of(numbers);
    if (end != std::string_view::npos)
        str.remove_suffix(std::size(str) - end);

    auto result = 0;
    auto multiplier = 1U;
    for (std::ptrdiff_t i = std::size(str) - 1U; i >= 0; --i) {
        auto number = str[i] - '0';
        result += number * multiplier * sign;
        multiplier *= 10U;
    }

    if (pos != nullptr) *pos = begin + std::size(str);
    return result;
}

main():

int main() {
    static_assert(mystoi(" 0   ") == 0);
    static_assert(mystoi(" 1   ") == 1);
    static_assert(mystoi("-1   ") == -1);
    static_assert(mystoi(" 12  ") == 12);
    static_assert(mystoi("-12  ") == -12);
    static_assert(mystoi(" 123 ") == 123);
    static_assert(mystoi("-123 ") == -123);
    static_assert(mystoi(" 1234") == 1234);
    static_assert(mystoi("-1234") == -1234);
    static_assert(mystoi("2147483647") == 2147483647);
    static_assert(mystoi("-2147483648") == -2147483648);
}
Voluntaryism answered 17/1, 2021 at 19:29 Comment(2)
This doesn't work with INT_MIN. Make is_negative an int with value 1 or -1 and use it like so result += number * multiplier * is_negative;Suh
@Suh :thumbsup:Voluntaryism
A
0

If you know the length of the string, you could do it with the preprocessor like this:

#define BINARY_STOI(X) (X[0]-'0') + 2*(X[1]-'0') \
            + 4*(X[2]-'0') + 8*(X[3]-'0') \
            + 16*(X[4]-'0') + 32*(X[5]-'0') \
            + 64*(X[6]-'0') + 128*(X[7]-'0')

#define DECIMAL_STOI(X) (X[0]-'0') + 10*(X[1]-'0') \
            + 100*(X[2]-'0') + 1000*(X[3]-'0') \
            + 10000*(X[4]-'0') + 100000*(X[5]-'0') \
            + 1000000*(X[6]-'0') + 10000000*(X[7]-'0')

And you just continue the pattern for however many chars you need.

Allotropy answered 1/4, 2023 at 16:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.