How do I generate an integer from a string literal at compile-time?
在 C 中,是否可以仅使用编译时工具从字符串文字生成整数?
例如,如果我们只有文字"6",有没有办法将它用作模板参数,例如
我知道基于
1 2 3 | template <int N> constexpr char get_char(const char s[N], int n) { return s[n]; } |
但是
这只是为了实验,所以欢迎疯狂的想法。
显然 gcc 允许将
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"。
它真正的作用是将字符串文字
1 | GetIntegerTemplate<'1', '2', '3', '4', '5', '6', '7'>::value |
然后调用
将列表转换为整数 1234567。 string → char 文字步骤可能涉及非标准行为,可能在 g 之外不起作用(即比
(转自我的另一个答案)
如果您不介意将 \\'string literal\\' 的概念定义从例如
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; |
实现了对负数的支持,不支持溢出检测(但您的编译器可能会给出警告)。
我不确定这是否可能,但这是你可以尝试的。
您可以将字符数字的值减去 \\'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,""); |