【问题标题】:C++ convert integer to string at compile timeC++ 在编译时将整数转换为字符串
【发布时间】:2011-10-06 12:07:30
【问题描述】:

我想做这样的事情:

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

似乎 boost MPL 库可能允许这样做,但我真的不知道如何使用它来实现这一点。这可能吗?

【问题讨论】:

  • 需要这样的功能吗?您可以使用预处理器宏更轻松地做到这一点(您只会失去类型安全性)。
  • itoa() 函数可以解决问题。 cplusplus.com/reference/clibrary/cstdlib/itoa
  • 定义编译时字符串...std::string 是运行时字符串。你可以做半运行时的魔法,但不能做纯编译时的。预处理器是最好的。
  • @Ram:OP 希望在 compile 期间进行转换。 itoa 函数在运行时工作。
  • @Thomas Matthews :) 我应该睡觉的迹象。请原谅我的笨拙。

标签: c++ string templates boost boost-mpl


【解决方案1】:

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

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

#define STRINGIFICATOR(X) #X

这样,无论何时你写STRINGIFICATOR(2),它都会产生“2”。

然而,它实际上可以在没有宏的情况下完成(使用编译时元编程)。这并不简单,所以我不能给出确切的代码,但我可以给你一些想法:

  1. 使用要转换的数字编写递归模板。模板会递归到base case,即数量小于10。
  2. 在每次迭代中,您可以将 N%10 位数字转换为 T.E.D. 字符。建议,使用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;
}

【讨论】:

  • 酷,我只是在做这个,但不知道如何连接字符:)
  • @yi_H: mpl 提供了push_front&lt;string_type,char&gt;::type 类型来定义一个编译时字符串,该字符串在现有字符串前面添加了char。
  • @Diego :您的代码似乎无法处理负值。另外,对于那些好奇的人,我前段时间写了这段代码的反面(使用mpl::string编译时间字符串到整数);代码可以在this answer找到。
  • @ildjam:是的,它不包括负数。我只是在业余时间做这个只是为了练习。添加负数支持不会太难,AFAIK。
  • 这太棒了。我不寒而栗地想到有多少大数字会增加编译时间......除非我错过了什么。
【解决方案2】:

我知道这个问题现在已经有几年了,但我想要一个使用纯 C++11 的解决方案,不依赖于 boost。所以这里有一些代码(从this answer to a different question借来的想法):

/* 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;
}

输出:

five = 5
one ten = 110
minus thirty = -30
eight cubed = 512

【讨论】:

  • 如何在 constexpr 函数的循环中调用它? numeric_builder 并且我的编译器说参数不是常量表达式?
【解决方案3】:

也许我错过了什么,但这应该很简单:

 #define NUM(x) #x

很遗憾,这不适用于非类型模板参数。

【讨论】:

  • 不过,这不适用于任意编译时间整数。 NUM(1+1)"1+1"
  • 为了允许使用其他宏作为参数,我建议额外的间接:#define _NUM(x) #x 后跟#define NUM(x) _NUM(x)
  • @hammar 枚举和已知的常量编译时值也不起作用。例如NUM(foo); 其中enum { foo = 42 };。宏将生成 "foo" 而不是 "42"
【解决方案4】:

我见过的一个技巧是在你知道你永远不会有一个超出0..9 范围的数字的情况下完成的:

return '0' + N;

乍一看,这是令人讨厌的限制。但是,我很惊讶这种情况出现了多少次。

哦,我知道这会返回 char 而不是 std::string。这是一个特点。 string 不是内置语言类型,因此无法在编译时创建。

【讨论】:

  • 是的,在这种情况下,整数可以是任何东西。
  • 这不符合返回char *的要求
【解决方案5】:

另一个有用的选项:

template <int i, bool gTen>
struct UintToStrImpl
{
   UintToStrImpl<i / 10, (i > 99)> c;
   const char c0 = '0' + i % 10;
};

template <int i>
struct UintToStrImpl <i, false> 
{ 
   const char c0 = '0' + i; 
};

template <int i, bool sign>
struct IntToStrImpl
{
   UintToStrImpl<i, (i > 9)> num_;
};

template <int i>
struct IntToStrImpl <i, false>
{
   const char sign = '-';
   UintToStrImpl<-i, (-i > 9)> num_;
};

template <int i>
struct IntToStr
{
   IntToStrImpl<i, (i >= 0)> num_;
   const char end = '\0';
   const char* str = (char*)this;
};

std::cout << IntToStr<-15450>().str;

【讨论】:

    【解决方案6】:

    这现在可以在 C++17 及更高版本中实现,无需任何外部依赖,转换完全在编译时完成。我已将代码打包在一个短头文件中,请查看 GitHub 上的 constexpr-to-string。该代码还支持不同的基数和字符宽度。

    使用示例:

    const char *number = to_string<2147483648999954564, 16>; // produces "1DCD65003B9A1884"
    puts(number);
    puts(to_string<-42>); // produces "-42"
    puts(to_string<30, 2>); // produces "11110"
    
    // Below requires C++20
    puts(f_to_string<3.1415926>); // Defaults to 5-point precision: "3.14159"
    puts(f_to_string<{3.1415926, 7}>); // Specify precision: "3.1415926"
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-21
      • 2021-08-15
      相关资源
      最近更新 更多