11

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

Rapptz
  • 20,807
  • 5
  • 72
  • 86
quant
  • 21,507
  • 32
  • 115
  • 211
  • 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. – Rapptz Aug 08 '14 at 02:22
  • I remember I have seen constexpr implementation of `atoi` somewhere is this site – Bryan Chen Aug 08 '14 at 02: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`? – Rapptz Aug 08 '14 at 02:26
  • In C++14, these `constexpr` functions become a lot more trivial. You could pretty much implement a naive `atoi` and mark it `constexpr`. – chris Aug 08 '14 at 02:47
  • Take a look here: http://enki-tech.blogspot.ca/2012/09/c11-compile-time-calculator-with.html , 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: http://stackoverflow.com/q/6713420/3093378 – vsoftco Aug 08 '14 at 03:02
  • @Rapptz I don't want to detect anything, I just want to do the conversion from `boost::mpl` to `int` at compile time. – quant Aug 08 '14 at 03:12
  • This isn't too hard if you use regular C strings. Unfortunately, I don't know much about `boost::mpl`. – Rapptz Aug 08 '14 at 03:18
  • @Rapptz well you're in luck because `boost::mpl::c_str::value` is a `constexpr` of type `char*` – quant Aug 08 '14 at 03:26

3 Answers3

17

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.

Rapptz
  • 20,807
  • 5
  • 72
  • 86
  • How does invalid-throwing work? When compile that expression, doesn't compiler cause error even if `is_digit(*str)` is `true`? – ikh Aug 09 '14 at 01:48
  • 2
    @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 Aug 09 '14 at 01:54
  • @Rapptz Oh, certainly we need a bit long template metaprogramming if that isn't exist >o – ikh Aug 09 '14 at 01:57
  • Please don't use nested ternary operators. – palapapa Jul 16 '23 at 21:43
  • 1
    @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. – Rapptz Jul 19 '23 at 05:40
1

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);
}
Oleksandr Kozlov
  • 697
  • 6
  • 11
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.