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).

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. Commented Aug 8, 2014 at 2:22
  • I remember I have seen constexpr implementation of atoi somewhere is this site Commented Aug 8, 2014 at 2:26
  • 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? Commented Aug 8, 2014 at 2:26
  • In C++14, these constexpr functions become a lot more trivial. You could pretty much implement a naive atoi and mark it constexpr. Commented Aug 8, 2014 at 2:47
  • 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: stackoverflow.com/q/6713420/3093378 Commented Aug 8, 2014 at 3:02

4 Answers 4

21

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.

Sign up to request clarification or add additional context in comments.

5 Comments

How does invalid-throwing work? When compile that expression, doesn't compiler cause error even if is_digit(*str) is true?
@ikh 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.
@Rapptz Oh, certainly we need a bit long template metaprogramming if that isn't exist >o<
Please don't use nested ternary operators.
@palapapa 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.
3

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);
}

2 Comments

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;
@Chnossos :thumbsup:
1

Starting with C++23, std::from_chars is now constexpr.

Example:

#include <charconv>
#include <iostream>
#include <string_view>

consteval int to_int(std::string_view str)
{
    int integer;
    auto result = std::from_chars(str.begin(), str.end(), integer);
    if (result.ptr != str.end() || result.ec != std::errc{})
    {
        throw;
    }
    return integer;
}

int main()
{
    constexpr int value = to_int("1234");
    std::cout << value;
}

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.