使用constexpr将数字转换为字符串文字

13

我正在寻找一种在编译时将数字转换为字符串文字的方法。它应该看起来像这样:

template <unsigned num>
struct num_to_string {
    constexpr static char value[] = /* ... magic goes here ... */;
};

以便 num_to_string<5>::value 等于 "5" 或者 {'5', '\0'}。这在通过一些其他 constexpr 数字计算的结果来在编译时生成字符串时非常有用。

还要注意,我只对无符号数字感兴趣,因为这应该更容易处理。如果有有符号版本的话,请额外加分 :)

编辑: 请注意,这类似于C++ convert integer to string at compile time,但并不完全相同。我明确希望使用constexpr而不是宏来辅助通用编程。


4
我认为三年的时间足够长,可以发现如何格式化代码块! - Lightness Races in Orbit
我并不完全确定这个“代码求助”问题是否严格符合主题,但我认为它足够有趣,值得留下来。也许稍后会尝试一下。 - Lightness Races in Orbit
1
C++11/14和C++0.786或C++0是一样的吗?嗯,你决定... - Lightness Races in Orbit
2
实际上,对于字面量来说这很简单:template<char... cs> auto operator"" _cs() -> std::array<char, sizeof...(cs)+1> { return {cs..., '\0'}; } 在线示例 - dyp
1
@dyp,这比你想象的还要简单:auto operator""_cs(const char* lit) { return lit; } - 实时示例 - tclamb
显示剩余8条评论
1个回答

38
可变参数模板来拯救我们。 :)
namespace detail
{
    template<unsigned... digits>
    struct to_chars { static const char value[]; };

    template<unsigned... digits>
    constexpr char to_chars<digits...>::value[] = {('0' + digits)..., 0};

    template<unsigned rem, unsigned... digits>
    struct explode : explode<rem / 10, rem % 10, digits...> {};

    template<unsigned... digits>
    struct explode<0, digits...> : to_chars<digits...> {};
}

template<unsigned num>
struct num_to_string : detail::explode<num> {};

一如既往,这里有一个Coliru上的实时示例,展示了用法和相关生成的汇编。


将此方法适应负数也很简单。这是一个更通用的形式,需要用户输入整数类型:

namespace detail
{
    template<uint8_t... digits> struct positive_to_chars { static const char value[]; };
    template<uint8_t... digits> constexpr char positive_to_chars<digits...>::value[] = {('0' + digits)..., 0};

    template<uint8_t... digits> struct negative_to_chars { static const char value[]; };
    template<uint8_t... digits> constexpr char negative_to_chars<digits...>::value[] = {'-', ('0' + digits)..., 0};

    template<bool neg, uint8_t... digits>
    struct to_chars : positive_to_chars<digits...> {};

    template<uint8_t... digits>
    struct to_chars<true, digits...> : negative_to_chars<digits...> {};

    template<bool neg, uintmax_t rem, uint8_t... digits>
    struct explode : explode<neg, rem / 10, rem % 10, digits...> {};

    template<bool neg, uint8_t... digits>
    struct explode<neg, 0, digits...> : to_chars<neg, digits...> {};

    template<typename T>
    constexpr uintmax_t cabs(T num) { return (num < 0) ? -num : num; }
}

template<typename Integer, Integer num>
struct string_from : detail::explode<(num < 0), detail::cabs(num)> {};

它的使用方式如下:

string_from<signed, -1>::value

Coliru上的实时演示中已经展示了相关的技术内容。

我非常喜欢这个解决方案,只有一点要提到:它不是真正的“constexpr”。当我想使用它并用“static_assert()”测试它时,我才意识到这一点。不过这并不是什么大问题:将“value”数组定义为“constexpr”,并在声明中初始化即可(必需)。 - Rene
为什么第一个template<unsigned... digits>和第二个template<uint8_t... digits>有不同的类型,它们不应该是相同的吗? - kyb
1
非常好。positive_to_chars需要一个额外的特化来处理数字恰好为零的情况。结果应该是“0”,而不是空字符串。 - Christopher Bruns
这仍然是做这件事的首选方式吗? - Moberg
感谢您的发现。可变参数模板非常美妙。这是递归和声明式编程的精妙应用! - D-FENS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接