关于 c :如何在编译时从字符串文字生成整数?

How do I generate an integer from a string literal at compile-time?

在 C 中,是否可以仅使用编译时工具从字符串文字生成整数?

例如,如果我们只有文字"6",有没有办法将它用作模板参数,例如 std::array<GET_INTEGER("6")> a; ?

我知道基于 constexpr 的技术,例如 :

1
2
3
template <int N> constexpr char get_char(const char s[N], int n) {
  return s[n];
}

但是 constexpr 在大多数编译器上还没有准备好,所以我正在寻找可能使用宏和 TMP 的解决方案。

这只是为了实验,所以欢迎疯狂的想法。

相关讨论

  • "大多数编译器"是什么意思?如果"大多数编译器"包括可能是最广泛使用的编译器(gcc 和 Visual C),那么答案是它无法完成。
  • 为什么不直接写 6,不带引号?
  • 如果您使用字符文字 GET_INTEGER('6', '7', '8'),它可能会起作用,但我认为字符串文字不会起作用。
  • @JamesMcNellis AFAIK,现在只有 gcc 支持 constexpr 。 (虽然我可能是错的)

显然 gcc 允许将 "abcd"[3] 解释为 'd',这允许它工作(至少在 g -4.6 和 4.7 上):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <boost/preprocessor/repetition/enum.hpp>

template <const char... characters>
struct GetIntegerTemplate;

template <const char head, const char... rest>
struct GetIntegerTemplate<head, rest...>
{
    typedef GetIntegerTemplate<rest...> Prev;
    enum
    {
        power = Prev::power * 10,
        value = (head - '0') * Prev::power + Prev::value
    };
};

template <>
struct GetIntegerTemplate<>
{
    enum
    {
        power = 1,
        value = 0
    };
};

#define GET_NTH_CHARACTER(z, n, data) data[n]
#define GET_INTEGER(length, the_string) GetIntegerTemplate<BOOST_PP_ENUM(length, GET_NTH_CHARACTER, the_string)>::value

int main()
{
    static_assert(GET_INTEGER(7,"1234567") == 1234567,"oops");
}

但它不会在 clang 上编译,它说 "non-type template argument of type \\'const char\\' is not an integer constant expression"。

它真正的作用是将字符串文字 "1234567" 分解为字符文字 '1', '2', '3', '4', '5', '6', '7' 的列表。实例化

1
GetIntegerTemplate<'1', '2', '3', '4', '5', '6', '7'>::value

然后调用

将列表转换为整数 1234567。 string → char 文字步骤可能涉及非标准行为,可能在 g 之外不起作用(即比 constexpr 差?),但 GetIntegerTemplate<...>::value 是便携。

相关讨论

  • 也许通过转换为 int ?我现在没有 LLVM,但这可能值得测试。
  • @kbok:问题不在于类型,而是clang中的 "1234567"[0] 不被视为常量。这可能是一个 gcc 扩展,但我找不到任何提到这一点的地方。
  • 我明白了,我认为问题在于它不是 integral。 MSVC 也不喜欢这样("\\'foo\\' 的模板参数无效,预期的编译时常量表达式");可惜只有 gcc 支持它,而它是唯一支持 constexpr 的编译器。
  • 从标准 (5.19.1) 开始,"整数常量表达式只能涉及算术类型 (2.13, 3.9.1)、枚举数、常量变量或整数或枚举类型的静态数据成员,这些成员使用常量表达式初始化 ( 8.5)、整数或枚举类型的非类型模板参数,以及 sizeof 表达式。" 所以,没有字符串文字。我认为 gcc 支持它作为 constexpr 支持的一部分。

(转自我的另一个答案)

如果您不介意将 \\'string literal\\' 的概念定义从例如
"425897"'4258','97',那么你可以使用 Boost.MPL 的 boost::mpl::string<> 来完成这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#include <cstddef>
#include <boost/type_traits/is_integral.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/type_traits/is_signed.hpp>
#include <boost/mpl/and.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/mpl/char.hpp>
#include <boost/mpl/contains.hpp>
#include <boost/mpl/end.hpp>
#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/find_if.hpp>
#include <boost/mpl/fold.hpp>
#include <boost/mpl/front.hpp>
#include <boost/mpl/identity.hpp>
#include <boost/mpl/integral_c.hpp>
#include <boost/mpl/minus.hpp>
#include <boost/mpl/negate.hpp>
#include <boost/mpl/next.hpp>
#include <boost/mpl/not.hpp>
#include <boost/mpl/pair.hpp>
#include <boost/mpl/placeholders.hpp>
#include <boost/mpl/plus.hpp>
#include <boost/mpl/pop_front.hpp>
#include <boost/mpl/push_back.hpp>
#include <boost/mpl/reverse_fold.hpp>
#include <boost/mpl/size_t.hpp>
#include <boost/mpl/string.hpp>
#include <boost/mpl/times.hpp>
#include <boost/mpl/vector.hpp>

namespace details
{
    namespace mpl = boost::mpl;

    typedef mpl::vector10<
        mpl::char_<'0'>, mpl::char_<'1'>, mpl::char_<'2'>, mpl::char_<'3'>,
        mpl::char_<'4'>, mpl::char_<'5'>, mpl::char_<'6'>, mpl::char_<'7'>,
        mpl::char_<'8'>, mpl::char_<'9'>
    > valid_chars_t;

    template<typename IntegralT, typename PowerT>
    struct power_of_10;

    template<typename IntegralT, std::size_t Power>
    struct power_of_10<IntegralT, mpl::size_t<Power> > : mpl::times<
        power_of_10<IntegralT, mpl::size_t<Power - 1u> >,
        mpl::integral_c<IntegralT, 10>
    > { };

    template<typename IntegralT>
    struct power_of_10<IntegralT, mpl::size_t<1u> >
        : mpl::integral_c<IntegralT, 10>
    { };

    template<typename IntegralT>
    struct power_of_10<IntegralT, mpl::size_t<0u> >
        : mpl::integral_c<IntegralT, 1>
    { };

    template<typename IntegralT, typename StringT>
    struct is_negative : mpl::and_<
        boost::is_signed<IntegralT>,
        boost::is_same<
            typename mpl::front<StringT>::type,
            mpl::char_<'-'>
        >
    > { };

    template<typename IntegralT, typename StringT>
    struct extract_actual_string : mpl::eval_if<
        is_negative<IntegralT, StringT>,
        mpl::pop_front<StringT>,
        mpl::identity<StringT>
    > { };

    template<typename ExtractedStringT>
    struct check_valid_characters : boost::is_same<
        typename mpl::find_if<
            ExtractedStringT,
            mpl::not_<mpl::contains<valid_chars_t, mpl::_> >
        >::type,
        typename mpl::end<ExtractedStringT>::type
    > { };

    template<typename ExtractedStringT>
    struct pair_digit_with_power : mpl::first<
        typename mpl::reverse_fold<
            ExtractedStringT,
            mpl::pair<mpl::vector0<>, mpl::size_t<0> >,
            mpl::pair<
                mpl::push_back<
                    mpl::first<mpl::_1>,
                    mpl::pair<mpl::_2, mpl::second<mpl::_1> >
                >,
                mpl::next<mpl::second<mpl::_1> >
            >
        >::type
    > { };

    template<typename IntegralT, typename ExtractedStringT>
    struct accumulate_digits : mpl::fold<
        typename pair_digit_with_power<ExtractedStringT>::type,
        mpl::integral_c<IntegralT, 0>,
        mpl::plus<
            mpl::_1,
            mpl::times<
                mpl::minus<mpl::first<mpl::_2>, mpl::char_<'0'> >,
                power_of_10<IntegralT, mpl::second<mpl::_2> >
            >
        >
    > { };

    template<typename IntegralT, typename StringT>
    class string_to_integral_impl
    {
        BOOST_MPL_ASSERT((boost::is_integral<IntegralT>));

        typedef typename extract_actual_string<
            IntegralT,
            StringT
        >::type ExtractedStringT;
        BOOST_MPL_ASSERT((check_valid_characters<ExtractedStringT>));

        typedef typename accumulate_digits<
            IntegralT,
            ExtractedStringT
        >::type ValueT;

    public:
        typedef typename mpl::eval_if<
            is_negative<IntegralT, StringT>,
            mpl::negate<ValueT>,
            mpl::identity<ValueT>
        >::type type;
    };
}

template<typename IntegralT, typename StringT>
struct string_to_integral2
    : details::string_to_integral_impl<IntegralT, StringT>::type
{ };

template<typename IntegralT, int C0, int C1 = 0, int C2 = 0,
    int C3 = 0, int C4 = 0, int C5 = 0, int C6 = 0, int C7 = 0>
struct string_to_integral : string_to_integral2<
    IntegralT,
    boost::mpl::string<C0, C1, C2, C3, C4, C5, C6, C7>
> { };

用法如下:

1
2
3
4
int i = string_to_integral<int, '4258','97'>::value;
// or
typedef boost::mpl::string<'4258','97'> str_t;
unsigned j = string_to_integral2<unsigned, str_t>::value;

实现了对负数的支持,不支持溢出检测(但您的编译器可能会给出警告)。

相关讨论

  • 这很有趣,但 '4258' 是什么?
  • @kbok:'4258','97' 是表示字符串文字 "425897" 的方式,它可以与 boost::mpl::string<> 一起使用。
  • @ildjarn:该示例代码是否需要所有这些提升?我想我记得为什么我讨厌使用 boost...
  • @kbok: '1234' 被称为多字符文字。它们作为 char 的表示基本上是实现定义的 IIRC。
  • @Chuck:由于某些 Boost 库、MPL、Spirit 等在编译器和预处理器上特别繁重,因此您只想包含绝对最低限度的必要条件。这会导致 #include 列表如此长,并且 MPL 通过不提供包含所有其他内容的简单 <mpl.hpp> 标头来强制它。但是,如果您重视编译时间,您将很乐意忽略包含的不便。
  • @Xeo:你说得对,表示是实现定义的。通常我会避免发布包含此类行为的答案,但在这种情况下,Boost 维护者会在足够多的平台/编译器上对代码进行广泛测试,让我觉得很舒服。
  • @ThatChuckGuy:除了 Xeo 所说的之外,Boost 不是一个库,而是一个库的集合——不要将您对 Boost 作为一个整体的不喜欢归咎于您不喜欢的一个库的设计。

我不确定这是否可能,但这是你可以尝试的。

您可以将字符数字的值减去 \\'0\\' 以获得数字形式的值。

喜欢:

1
2
char a = '5';
int num =  a - '0';

这将解决您的问题一位数。

要求解一个多位数的数字(如"12345"),您必须循环所有数字并对结果求和(每个数字乘以 10^pos)。

这在执行时很容易做到,但在编译时就不那么简单了。

"compile time recursion" 可能是你的朋友。老实说,我想不出任何使用它的解决方案,但你可能会找到一个。

祝你好运!


也许?

1
2
3
4
5
6
7
template<int C>
struct get_char
{
    static const int value = C - 48;
};

static_assert(get_char<'0'>::value == 0,"");


以上是关于 c :如何在编译时从字符串文字生成整数?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>