C++在编译时将整数转换为字符串

And*_*rey 17 c++ string templates boost boost-mpl

我想做这样的事情:

template<int N>
char* foo() {
  // return a compile-time string containing N, equivalent to doing
  // ostringstream ostr; 
  // ostr << N;
  // return ostr.str().c_str();
}
Run Code Online (Sandbox Code Playgroud)

似乎boost MPL库可能允许这样但我无法弄清楚如何使用它来实现这一点.这可能吗?

Die*_*lla 24

首先,如果通常你知道运行时的数字,你可以轻松地构建相同的字符串.也就是说,如果你有12你的程序,你也可以"12".

预处理器宏可以在参数中添加引号,因此您可以编写:

#define STRINGIFICATOR(X) #X
Run Code Online (Sandbox Code Playgroud)

这样,无论何时写STRINGIFICATOR(2),它都会产生"2".

但是,它实际上可以在没有宏的情况下完成(使用编译时元编程).这不是直截了当的,所以我不能给出确切的代码,但我可以给你如何做的想法:

  1. 使用要转换的数字编写递归模板.模板将递归到基本情况,即数字小于10.
  2. 在每次迭代中,您可以将N%10数字转换为TED建议的字符,使用mpl::string构建附加该字符的编译时字符串.
  3. 你最终会建立一个mpl::string有静态value()字符串的东西.

我花时间将其作为个人练习来实施.最后还不错:

#include <iostream>
#include <boost/mpl/string.hpp>

using namespace boost;

// Recursive case
template <bool b, unsigned N>
struct int_to_string2
{
        typedef typename mpl::push_back<
                typename int_to_string2< N < 10, N/10>::type
                                         , mpl::char_<'0' + N%10>
                                         >::type type;
};

// Base case
template <>
struct int_to_string2<true,0>
{
        typedef mpl::string<> type;
};


template <unsigned N>
struct int_to_string
{
        typedef typename mpl::c_str<typename int_to_string2< N < 10 , N>::type>::type type;
};

int
main (void)
{
        std::cout << int_to_string<1099>::type::value << std::endl;
        return 0;
}
Run Code Online (Sandbox Code Playgroud)


Joh*_*ren 19

我知道这个问题现在已经有几年了,但是我想要一个使用纯C++ 11的解决方案,没有增强依赖性.所以这里有一些代码(从这个问题的答案借用了另一个问题):

/* IMPLEMENTATION */

/* calculate absolute value */
constexpr int abs_val (int x)
    { return x < 0 ? -x : x; }

/* calculate number of digits needed, including minus sign */
constexpr int num_digits (int x)
    { return x < 0 ? 1 + num_digits (-x) : x < 10 ? 1 : 1 + num_digits (x / 10); }

/* metaprogramming string type: each different string is a unique type */
template<char... args>
struct metastring {
    const char data[sizeof... (args)] = {args...};
};

/* recursive number-printing template, general case (for three or more digits) */
template<int size, int x, char... args>
struct numeric_builder {
    typedef typename numeric_builder<size - 1, x / 10, '0' + abs_val (x) % 10, args...>::type type;
};

/* special case for two digits; minus sign is handled here */
template<int x, char... args>
struct numeric_builder<2, x, args...> {
    typedef metastring<x < 0 ? '-' : '0' + x / 10, '0' + abs_val (x) % 10, args...> type;
};

/* special case for one digit (positive numbers only) */
template<int x, char... args>
struct numeric_builder<1, x, args...> {
    typedef metastring<'0' + x, args...> type;
};

/* convenience wrapper for numeric_builder */
template<int x>
class numeric_string
{
private:
    /* generate a unique string type representing this number */
    typedef typename numeric_builder<num_digits (x), x, '\0'>::type type;

    /* declare a static string of that type (instantiated later at file scope) */
    static constexpr type value {};

public:
    /* returns a pointer to the instantiated string */
    static constexpr const char * get ()
        { return value.data; }
};

/* instantiate numeric_string::value as needed for different numbers */
template<int x>
constexpr typename numeric_string<x>::type numeric_string<x>::value;

/* SAMPLE USAGE */

#include <stdio.h>

/* exponentiate a number, just for fun */
static constexpr int exponent (int x, int e)
    { return e ? x * exponent (x, e - 1) : 1; }

/* test a few sample numbers */
static constexpr const char * five = numeric_string<5>::get ();
static constexpr const char * one_ten = numeric_string<110>::get ();
static constexpr const char * minus_thirty = numeric_string<-30>::get ();

/* works for any constant integer, including constexpr calculations */
static constexpr const char * eight_cubed = numeric_string<exponent (8, 3)>::get ();

int main (void)
{
    printf ("five = %s\n", five);
    printf ("one ten = %s\n", one_ten);
    printf ("minus thirty = %s\n", minus_thirty);
    printf ("eight cubed = %s\n", eight_cubed);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出:

five = 5
one ten = 110
minus thirty = -30
eight cubed = 512
Run Code Online (Sandbox Code Playgroud)


cly*_*yne 14

这可以使用 C++14 来完成,无需任何外部依赖项。该标准的关键补充是能够拥有重要的constexpr构造函数,从而允许将功能包含在一个简单的类中。

给定一个整数模板参数,构造函数可以执行整数到字符串的转换。它存储在成员字符缓冲区中,其大小由附加函数确定constexpr。然后,用户定义的转换提供对缓冲区的访问:

#include <cstdint>

template<std::intmax_t N>
class to_string_t {

    constexpr static auto buflen() noexcept {
        unsigned int len = N > 0 ? 1 : 2;
        for (auto n = N; n; len++, n /= 10);
        return len;
    }

    char buf[buflen()] = {};

public:
    constexpr to_string_t() noexcept {
        auto ptr = buf + buflen();
        *--ptr = '\0';

        if (N != 0) {
            for (auto n = N; n; n /= 10)
                *--ptr = "0123456789"[(N < 0 ? -1 : 1) * (n % 10)];
            if (N < 0)
                *--ptr = '-';
        } else {
            buf[0] = '0';
        }
    }

    constexpr operator const char *() const { return buf; }
};
Run Code Online (Sandbox Code Playgroud)

最后,变量模板(另一个 C++14 添加)简化了语法:

template<std::intmax_t N>
constexpr to_string_t<N> to_string;

puts(to_string<62017>); // prints "62017"
Run Code Online (Sandbox Code Playgroud)

该功能可以扩展以支持其他基数(例如十六进制)、宽字符类型和通用容器接口;我已将所有内容打包到一个标头中,并将其放在 GitHub 上,网址为tcsullivan/constexpr-to-string

使用 C++20,还可以扩展以支持浮点数。浮点文字需要一个容器类型,以前不能作为模板参数。请参阅f_to_string.hppGitHub 存储库中的标头以了解实现。


Kar*_*ath 6

也许我错过了一些东西,但这应该很简单:

 #define NUM(x) #x
Run Code Online (Sandbox Code Playgroud)

不幸的是,这不适用于非类型模板参数。

  • 但是,这不适用于任意编译时整数。`NUM(1+1)` 给出了 `"1+1"`。 (5认同)
  • @hammar 枚举和已知的常量编译时值也不起作用。例如`NUM(foo);`,其中`enum { foo = 42 };`。宏将产生 `"foo"` 而不是 `"42"`。 (4认同)
  • 为了允许将其他宏用作参数,我建议使用额外的间接寻址:`#define _NUM(x) #x` 后跟 `#define NUM(x) _NUM(x)`。 (2认同)