arg_router  1.4.0
C++ command line argument parsing and routing
compile_time_string.hpp
Go to the documentation of this file.
1 // Copyright (C) 2022-2023 by Camden Mannett.
2 // Distributed under the Boost Software License, Version 1.0.
3 // (See accompanying file LICENSE or copy at https://www.boost.org/LICENSE_1_0.txt)
4 
5 #pragma once
6 
7 #include "arg_router/config.hpp"
8 #include "arg_router/math.hpp"
9 
10 #include <array>
11 
12 #ifdef AR_ENABLE_CPP20_STRINGS
13 # include <algorithm>
14 # include <span>
15 # include <string_view>
16 
17 namespace arg_router
18 {
19 namespace utility
20 {
21 namespace detail
22 {
23 template <std::size_t N>
24 class compile_time_string_storage
25 {
26 public:
27  constexpr compile_time_string_storage() = default;
28 
29  // We need all the constructors to support implicit conversion, because that's kind of the
30  // point of this class...
31  // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions)
32  constexpr compile_time_string_storage(std::array<char, N> str) : value(str) {}
33 
34  // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions)
35  constexpr compile_time_string_storage(std::span<const char, N> str)
36  {
37  std::copy(str.begin(), str.end(), value.begin());
38  }
39 
40  // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions,*-c-arrays)
41  constexpr compile_time_string_storage(const char (&str)[N])
42  {
43  std::copy_n(&str[0], N, value.begin());
44  }
45 
46  // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions)
47  constexpr compile_time_string_storage(char c) : value{c} {}
48 
49  std::array<char, N> value;
50 };
51 
52 // This looks pointless - that's because it is! It's a workaround for a MSVC bug:
53 // https://developercommunity.visualstudio.com/t/Uninitialized-read-of-symbol-in-std::arr/10404384
54 template <>
55 class compile_time_string_storage<0>
56 {
57 public:
58  struct empty_t {
59  constexpr std::size_t size() const { return 0; }
60  constexpr const char* data() const { return nullptr; }
61  constexpr char operator[](std::size_t) const { return '\n'; }
62  };
63 
64  constexpr compile_time_string_storage() = default;
65 
66  // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions)
67  constexpr compile_time_string_storage(std::array<char, 0>) {}
68 
69  // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions)
70  constexpr compile_time_string_storage(std::span<const char, 0>) {}
71 
72  // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions,*-c-arrays)
73  constexpr compile_time_string_storage(const char (&)[0]) {}
74 
75  empty_t value;
76 };
77 
78 compile_time_string_storage()->compile_time_string_storage<0>;
79 compile_time_string_storage(char)->compile_time_string_storage<1>;
80 } // namespace detail
81 
94 template <detail::compile_time_string_storage S = detail::compile_time_string_storage<0>{}>
95 class str
96 {
97 public:
102  [[nodiscard]] constexpr static std::size_t size() noexcept { return size_; }
103 
108  [[nodiscard]] constexpr static bool empty() noexcept { return size() == 0; }
109 
114  [[nodiscard]] constexpr static std::string_view get() noexcept
115  {
116  return {S.value.data(), size()};
117  }
118 
125  template <detail::compile_time_string_storage S2>
126  [[nodiscard]] constexpr auto operator+([[maybe_unused]] const str<S2>& other) const noexcept
127  {
128  return []<std::size_t... LIs, std::size_t... RIs>(std::index_sequence<LIs...>,
129  std::index_sequence<RIs...>)
130  {
131  return str<std::array{S.value[LIs]..., S2.value[RIs]..., '\0'}>{};
132  }
133  (std::make_index_sequence<size()>{}, std::make_index_sequence<str<S2>::size()>{});
134  }
135 
143  template <std::size_t Pos, std::size_t Count>
144  [[nodiscard]] constexpr static auto substr() noexcept
145  {
146  static_assert((Pos + Count) < size_, "Pos+Count must be less than string size");
147 
148  return substr_impl<Pos>(std::make_index_sequence<Count>{});
149  }
150 
155  template <typename T>
156  struct append;
157 
158  template <detail::compile_time_string_storage S2>
159  struct append<str<S2>> {
160  using type = std::decay_t<decltype(str{} + str<S2>{})>;
161  };
162 
167  template <typename T>
168  using append_t = typename append<T>::type;
169 
170 private:
171  // This is a necessary evil, as we can't use std::char_traits<char>::length(..) in a
172  // constant expression
173  constexpr static std::size_t calculate_size() noexcept
174  {
175  auto count = std::size_t{0};
176  for (; count < S.value.size(); ++count) {
177  if (S.value[count] == '\0') {
178  break;
179  }
180  }
181  return count;
182  }
183 
184  template <std::size_t Pos, std::size_t... Is>
185  constexpr static auto substr_impl([[maybe_unused]] std::index_sequence<Is...> seq)
186  {
187  return str<std::array{S.value[Pos + Is]..., '\0'}>{};
188  }
189 
190  constexpr static std::size_t size_ = calculate_size();
191 };
192 
198 template <typename S1, typename S2>
199 using str_concat = typename S1::template append_t<S2>;
200 
206 template <std::size_t N, char C>
207 class create_sequence_cts
208 {
209  template <std::size_t>
210  [[nodiscard]] constexpr static char get() noexcept
211  {
212  return C;
213  }
214 
215  template <std::size_t... Is>
216  [[nodiscard]] constexpr static std::array<char, N> builder(
217  [[maybe_unused]] std::index_sequence<Is...> seq) noexcept
218  {
219  return {get<Is>()...};
220  }
221 
222 public:
223  using type = str<builder(std::make_index_sequence<N>{})>;
224 };
225 
231 template <std::size_t N, char C>
232 using create_sequence_cts_t = typename create_sequence_cts<N, C>::type;
233 
238 template <auto Value>
239 struct convert_integral_to_cts {
240 private:
241  static_assert(std::is_integral_v<decltype(Value)>, "Value must be an integral");
242 
243  template <typename Str, auto NewValue>
244  [[nodiscard]] constexpr static auto build() noexcept
245  {
246  constexpr auto num_digits = math::num_digits(NewValue);
247  constexpr auto power10 = math::pow<10>(num_digits - 1);
248  constexpr auto digit = NewValue / power10;
249 
250  using digit_str = str<'0' + digit>;
251 
252  if constexpr (num_digits != 1) {
253  constexpr auto next_value = NewValue % power10;
254  return build<typename Str::template append_t<digit_str>, next_value>();
255  } else {
256  return typename Str::template append_t<digit_str>{};
257  }
258  }
259 
260  using digit_str = decltype(build<str<>, math::abs(Value)>());
261 
262 public:
263  using type = std::conditional_t<(Value < 0), str<'-'>::template append_t<digit_str>, digit_str>;
264 };
265 
270 template <auto Value>
271 using convert_integral_to_cts_t = typename convert_integral_to_cts<Value>::type;
272 } // namespace utility
273 
278 template <utility::detail::compile_time_string_storage S>
279 using str = utility::str<S>;
280 
281 namespace traits
282 {
287 template <typename T>
288 struct is_compile_time_string_like : std::false_type {
289 };
290 
291 template <auto S>
292 struct is_compile_time_string_like<utility::str<S>> : std::true_type {
293 };
294 
299 template <typename T>
300 constexpr bool is_compile_time_string_like_v = is_compile_time_string_like<T>::value;
301 } // namespace traits
302 } // namespace arg_router
303 
304 // A function macro common between utility::str and S_, which allows us to use the same method to
305 // construct strings internally between versions. NOT for user consumption!
306 # define AR_STRING(tok) arg_router::utility::str<tok>
307 # define AR_STRING_SV(tok) arg_router::utility::str<std::span<const char, tok.size()>{tok}>
308 
309 #else
310 
311 # include "arg_router/traits.hpp"
312 
313 # include <boost/mp11/algorithm.hpp>
314 # include <boost/preprocessor/repetition/enum.hpp>
315 
319 namespace arg_router
320 {
321 namespace utility
322 {
323 template <char... Cs>
324 class compile_time_string;
325 
330 template <char... Cs>
332 {
333 public:
335  using array_type = std::tuple<traits::integral_constant<Cs>...>;
336 
341  [[nodiscard]] constexpr static std::size_t size() noexcept { return sizeof...(Cs); }
342 
347  [[nodiscard]] constexpr static bool empty() noexcept { return size() == 0; }
348 
353  [[nodiscard]] constexpr static std::string_view get() noexcept
354  {
355  return {sv_.data(), sv_.size()};
356  }
357 
362  template <typename T>
363  struct append;
364 
365  template <char... OtherCs>
366  struct append<compile_time_string<OtherCs...>> {
367  using type = compile_time_string<Cs..., OtherCs...>;
368  };
369 
374  template <typename T>
375  using append_t = typename append<T>::type;
376 
383  template <char... OtherCs>
384  [[nodiscard]] constexpr auto operator+(
385  [[maybe_unused]] const compile_time_string<OtherCs...>& other) const noexcept
386  {
387  return append_t<compile_time_string<OtherCs...>>{};
388  }
389 
390 private:
391  constexpr static auto sv_ = std::array<char, sizeof...(Cs)>{Cs...};
392 };
393 
399 template <std::size_t N, char C>
401 {
402  using seq = boost::mp11::mp_repeat_c<std::tuple<traits::integral_constant<C>>, N>;
403 
404  template <typename T>
405  struct converter {
406  };
407 
408  template <typename... Cs>
409  struct converter<std::tuple<Cs...>> {
410  using type = compile_time_string<Cs::value...>;
411  };
412 
413 public:
414  using type = typename converter<seq>::type;
415 };
416 
422 template <std::size_t N, char C>
423 using create_sequence_cts_t = typename create_sequence_cts<N, C>::type;
424 
429 template <typename T>
431 
432 template <template <typename...> typename Array, typename... Cs>
433 struct convert_to_cts<Array<Cs...>> {
434  using type = utility::compile_time_string<Cs::value...>;
435 };
436 
441 template <typename T>
443 
448 template <auto Value>
450 private:
451  static_assert(std::is_integral_v<decltype(Value)>, "Value must be an integral");
452 
453  template <typename Str, auto NewValue>
454  [[nodiscard]] constexpr static auto build() noexcept
455  {
456  constexpr auto num_digits = math::num_digits(NewValue);
457  constexpr auto power10 = math::pow<10>(num_digits - 1);
458  constexpr auto digit = NewValue / power10;
459 
460  using digit_str = typename Str::template append_t<compile_time_string<'0' + digit>>;
461 
462  if constexpr (num_digits != 1) {
463  constexpr auto next_value = NewValue % power10;
464  return build<digit_str, next_value>();
465  } else {
466  return digit_str{};
467  }
468  }
469 
470  using digit_str = decltype(build<compile_time_string<>, math::abs(Value)>());
471 
472 public:
473  using type = std::conditional_t<(Value < 0),
474  compile_time_string<'-'>::template append_t<digit_str>,
475  digit_str>;
476 };
477 
482 template <auto Value>
483 using convert_integral_to_cts_t = typename convert_integral_to_cts<Value>::type;
484 
485 namespace cts_detail
486 {
487 template <int N>
488 // NOLINTNEXTLINE(*-avoid-c-arrays)
489 [[nodiscard]] constexpr char get(const char (&str)[N], std::size_t i) noexcept
490 {
491  return i < N ? str[i] : '\0';
492 }
493 
494 [[nodiscard]] constexpr char get(std::string_view str, std::size_t i) noexcept
495 {
496  return i < str.size() ? str[i] : '\0';
497 }
498 
499 [[nodiscard]] constexpr char get(char c, std::size_t i) noexcept
500 {
501  return i < 1 ? c : '\0';
502 }
503 
504 // Required so that the extra nulls S_ adds can be removed before defining the compile_time_string.
505 // Otherwise any compiler warnings you hit are just walls of null template args...
506 template <char... Cs>
507 struct builder {
508  struct is_null_char {
509  template <typename T>
510  using fn = std::is_same<std::integral_constant<char, '\0'>, T>;
511  };
512 
513  using strip_null =
514  boost::mp11::mp_remove_if_q<boost::mp11::mp_list_c<char, Cs...>, is_null_char>;
515 
516  static_assert(std::tuple_size_v<strip_null> < AR_MAX_CTS_SIZE,
517  "Compile-time string limit reached, consider increasing AR_MAX_CTS_SIZE");
518 
519  template <char... StrippedCs>
520  [[nodiscard]] constexpr static auto list_to_string(
521  [[maybe_unused]] boost::mp11::mp_list_c<char, StrippedCs...> chars) noexcept
522  {
523  return compile_time_string<StrippedCs...>{};
524  }
525 
526  using type = decltype(list_to_string(strip_null{}));
527 };
528 
529 template <char... Cs>
530 using builder_t = typename builder<Cs...>::type;
531 } // namespace cts_detail
532 } // namespace utility
533 
534 namespace traits
535 {
540 template <typename T>
541 struct is_compile_time_string_like : std::false_type {
542 };
543 
544 template <char... Cs>
545 struct is_compile_time_string_like<utility::compile_time_string<Cs...>> : std::true_type {
546 };
547 
552 template <typename T>
554 } // namespace traits
555 } // namespace arg_router
556 
557 # define AR_STR_CHAR(z, n, tok) arg_router::utility::cts_detail::get(tok, n)
558 
559 # define AR_STR_N(n, tok) \
560  arg_router::utility::cts_detail::builder_t<BOOST_PP_ENUM(n, AR_STR_CHAR, tok)>
561 
572 # define S_(tok) AR_STR_N(AR_MAX_CTS_SIZE, tok)
573 
574 // A function macro common between utility::str and S_, which allows us to use the same method to
575 // construct strings internally between versions. NOT for user consumption!
576 # define AR_STRING(tok) S_(tok)
577 # define AR_STRING_SV(tok) S_(tok)
578 #endif
std::tuple< traits::integral_constant< Cs >... > array_type
constexpr auto operator+([[maybe_unused]] const compile_time_string< OtherCs... > &other) const noexcept
constexpr static std::string_view get() noexcept
constexpr static bool empty() noexcept
constexpr static std::size_t size() noexcept
constexpr T num_digits(T value) noexcept
Definition: math.hpp:38
constexpr T abs(T value) noexcept
Definition: math.hpp:20
constexpr bool is_compile_time_string_like_v
constexpr std::size_t count(std::string_view str) noexcept
Definition: code_point.hpp:129
constexpr std::size_t size(std::string_view str) noexcept
Definition: code_point.hpp:148
typename create_sequence_cts< N, C >::type create_sequence_cts_t
typename convert_integral_to_cts< Value >::type convert_integral_to_cts_t
typename convert_to_cts< T >::type convert_to_cts_t
dynamic_string_view operator+(dynamic_string_view a, T &&b)