将参数包中的`consteval`表达式与非`consteval`表达式分离

huangapple go评论101阅读模式
英文:

Splitting consteval from non-consteval expressions in parameter packs

问题

我的目标是使用 C++20 编写一个新的格式库,该库可以写入一个通常如下所示的日志行:

  1. Log(
  2. Error,
  3. If(result == false),
  4. Title("Operation failed"),
  5. "Failed to execute task, ID: {}",
  6. taskId);

它接受:

  1. 任意数量的 "日志特性"(ErrorIfTitle),在运行时进行评估
  2. 格式化字符串 + 任意数量的参数作为消息,使用 std::format_to 进行格式化

问题是,我想使用 std::format_to 进行其编译时格式检查,这要求字符串 "Failed to execute task, ID: {}"" 必须保持为 consteval,以便将其转换为 std::format_string<...>,但我不希望每个 "日志特性" 也都是 consteval,因为有些需要在运行时评估,比如 If

我的当前尝试是定义一个 LogTrait 概念,所有 ErrorIfTitle 都遵循:

  1. template<typename T> concept LogTrait = ...

我用它来尝试跳过所有特性,直接到达格式化字符串:

  1. template<typename... Args>
  2. consteval auto GetFormatString(std::format_string<Args...> fmt, Args&&... args)
  3. {
  4. return std::make_pair(fmt, std::make_format_args(std::forward<Args>(args)...));
  5. }
  6. template<typename... Args>
  7. constexpr auto GetFormatString(LogTrait auto&&, Args&&... args)
  8. {
  9. return GetFormatString(std::forward<Args>(args)...);
  10. }

但如果我将这两个函数都设为 consteval,会出错,因为 If 不能在编译时解析,如果我将任一函数仅设置为 constexpr,那么格式化字符串就无法转换为 std::format_string,因为它只能在 consteval 构造时转换。

有什么想法吗?

英文:

My goal is to use C++20 to write a new format library, which can write a log line that generally looks like this:

  1. Log(
  2. Error,
  3. If(result == false),
  4. Title("Operation failed"),
  5. "Failed to execute task, ID: {}",
  6. taskId);

Which takes:

  1. Any number of "Log Traits" (Error, If, Title), to be evaluated at runtime
  2. A format string + any number of args as the message, to be formatted using std::format_to

The issue is that I want to use std::format_to for its compile type format checking, which requires that the string "Failed to execute task, ID: {}" must remain consteval for it to be turned into a std::format_string<...>, but I don't want every "Log Trait" to also be consteval, because some need to be evaluated at runtime, such as If.

My current attempt has me defining a LogTrait concept, which Error, If and Title all adhere to:

  1. template<typename T> concept LogTrait = ...

That I use to try to skip all the traits, and get right to the format string:

  1. template<typename... Args>
  2. consteval auto GetFormatString(std::format_string<Args...> fmt, Args&&... args)
  3. {
  4. return std::make_pair(fmt, std::make_format_args(std::forward<Args>(args)...));
  5. }
  6. template<typename... Args>
  7. constexpr auto GetFormatString(LogTrait auto&&, Args&&... args)
  8. {
  9. return GetFormatString(std::forward<Args>(args)...);
  10. }

But if I make both functions consteval, it'll error because If can't be resolved at compile time, and if I make either function merely constexpr, then the format string can't be converted to std::format_string since it can only be consteval constructed.

Any thoughts?

答案1

得分: 4

以下是翻译好的部分:

  1. 最简单的方法是通过更改调用`Log`函数的方式来绕过此问题:
  2. ```c++
  3. Log
  4. .Error
  5. .If(result == false)
  6. .Title("Operation failed")
  7. ("Failed to execute task, ID: {}", taskId);
  8. Log(
  9. Error,
  10. If(result == false),
  11. Title("Operation failed")
  12. )("Failed to execute task, ID: {}",
  13. taskId);
  14. // 或者创建一个新的“LogTrait”来保存格式字符串及其参数
  15. Log(
  16. Error,
  17. If(result == false),
  18. Title("Operation {} failed", name), // 这也应该是一个格式字符串
  19. Body("Failed to execute task, ID: {}", taskId));

尽管保持当前的语法是可能的,但也很困难。一旦您将"Failed to execute task, ID: {}"传递为const char(&)[N]const char*参数,您就无法构建一个format_string(因为它不能是consteval),并且使用vformat会导致类型安全性丧失,因此您必须采取其他措施。

因此,您必须使Log成为一个模板结构,以便可以推断参数的类型,然后推断Log结构的类型,以便您的format_string可以具有正确的类型。

假设这是您要调用的最终函数:

  1. template<LogTrait... Traits, typename... Args>
  2. void do_log(std::tuple<Traits...> traits, std::format_string<Args...> fmt, Args&&... args) {
  3. // ...
  4. }

您可以像这样实现Log

  1. template<typename Traits, typename Args>
  2. struct Log;
  3. template<typename... Traits, typename... Args>
  4. struct Log<std::tuple<Traits...>, std::tuple<Args...>>{
  5. Log(const Log&) = delete;
  6. Log& operator=(const Log&) = delete;
  7. ~Log() = default;
  8. Log(Traits&&... traits, std::format_string<Args...> fmt, Args&&... args) {
  9. do_log(std::forward_as_tuple(std::forward<Traits>(traits)...), fmt, std::forward<Args>(args)...);
  10. }
  11. };
  12. template<std::size_t Offset, typename... Args, std::size_t... I>
  13. auto tuple_index(std::index_sequence<I...>) -> std::tuple<std::tuple_element_t<Offset + I, std::tuple<Args...>>...>;
  14. template<std::size_t N, typename... Args>
  15. using tuple_of_first = decltype(tuple_index<0, Args...>(std::make_index_sequence<N>{}));
  16. template<std::size_t N, typename... Args>
  17. using tuple_of_last = decltype(tuple_index<sizeof...(Args)-N, Args...>(std::make_index_sequence<N>{}));
  18. // 第一个非LogTrait参数的索引,即格式字符串
  19. template<typename... Args>
  20. inline constexpr std::size_t format_string_index = []<std::size_t... I>(std::index_sequence<I...>) {
  21. constexpr std::size_t format_string_index = std::min({ LogTrait<Args> ? std::size_t(-1) : I... });
  22. static_assert(format_string_index != std::size_t(-1), "Log(...): No format string");
  23. return format_string_index;
  24. }(std::index_sequence_for<Args...>{});
  25. template<typename... Args>
  26. struct traits_argument {
  27. using type = tuple_of_first<format_string_index<Args...>, Args...>;
  28. };
  29. template<typename... Args>
  30. struct format_argument {
  31. using type = tuple_of_last<sizeof...(Args) - 1u - format_string_index<Args...>, Args...>;
  32. };
  33. template<typename... Args>
  34. Log(Args&&... args) -> Log<typename traits_argument<Args...>::type, typename format_argument<Args...>::type>;

https://godbolt.org/z/EbrYheYPW

  1. <details>
  2. <summary>英文:</summary>
  3. The easiest way would be to sidestep this by changing how you call the `Log` function:
  4. ```c++
  5. Log
  6. .Error
  7. .If(result == false)
  8. .Title(&quot;Operation failed&quot;)
  9. (&quot;Failed to execute task, ID: {}&quot;, taskId);
  10. Log(
  11. Error,
  12. If(result == false),
  13. Title(&quot;Operation failed&quot;)
  14. )(&quot;Failed to execute task, ID: {}&quot;,
  15. taskId);
  16. // Or make a new &quot;LogTrait&quot; to hold the format string with it&#39;s args
  17. Log(
  18. Error,
  19. If(result == false),
  20. Title(&quot;Operation {} failed&quot;, name), // This should also be a format string
  21. Body(&quot;Failed to execute task, ID: {}&quot;, taskId));

It is possible, albeit difficult, to maintain your current syntax. Once you've passed &quot;Failed to execute task, ID: {}&quot; as a const char(&amp;)[N] or const char* argument, you can't construct a format_string (since it can't be consteval), and using vformat gets rid of type safety, so you have to do something else.

So, you have to make Log a template struct, so you can deduce the type of the arguments then deduce the type of the Log struct so your format_string can have the right type.

Say this is the ultimate function you want to call:

  1. template&lt;LogTrait... Traits, typename... Args&gt;
  2. void do_log(std::tuple&lt;Traits...&gt; traits, std::format_string&lt;Args...&gt; fmt, Args&amp;&amp;... args) {
  3. // ...
  4. }

You can implement Log like this:

  1. template&lt;typename Traits, typename Args&gt;
  2. struct Log;
  3. template&lt;typename... Traits, typename... Args&gt;
  4. struct Log&lt;std::tuple&lt;Traits...&gt;, std::tuple&lt;Args...&gt;&gt;{
  5. Log(const Log&amp;) = delete;
  6. Log&amp; operator=(const Log&amp;) = delete;
  7. ~Log() = default;
  8. Log(Traits&amp;&amp;... traits, std::format_string&lt;Args...&gt; fmt, Args&amp;&amp;... args) {
  9. do_log(std::forward_as_tuple(std::forward&lt;Traits&gt;(traits)...), fmt, std::forward&lt;Args&gt;(args)...);
  10. }
  11. };
  12. template&lt;std::size_t Offset, typename... Args, std::size_t... I&gt;
  13. auto tuple_index(std::index_sequence&lt;I...&gt;) -&gt; std::tuple&lt;std::tuple_element_t&lt;Offset + I, std::tuple&lt;Args...&gt;&gt;...&gt;;
  14. template&lt;std::size_t N, typename... Args&gt;
  15. using tuple_of_first = decltype(tuple_index&lt;0, Args...&gt;(std::make_index_sequence&lt;N&gt;{}));
  16. template&lt;std::size_t N, typename... Args&gt;
  17. using tuple_of_last = decltype(tuple_index&lt;sizeof...(Args)-N, Args...&gt;(std::make_index_sequence&lt;N&gt;{}));
  18. // Index of the first non-LogTrait argument, the format string
  19. template&lt;typename... Args&gt;
  20. inline constexpr std::size_t format_string_index = []&lt;std::size_t... I&gt;(std::index_sequence&lt;I...&gt;) {
  21. constexpr std::size_t format_string_index = std::min({ LogTrait&lt;Args&gt; ? std::size_t(-1) : I... });
  22. static_assert(format_string_index != std::size_t(-1), &quot;Log(...): No format string&quot;);
  23. return format_string_index;
  24. }(std::index_sequence_for&lt;Args...&gt;{});
  25. template&lt;typename... Args&gt;
  26. struct traits_argument {
  27. using type = tuple_of_first&lt;format_string_index&lt;Args...&gt;, Args...&gt;;
  28. };
  29. template&lt;typename... Args&gt;
  30. struct format_argument {
  31. using type = tuple_of_last&lt;sizeof...(Args) - 1u - format_string_index&lt;Args...&gt;, Args...&gt;;
  32. };
  33. template&lt;typename... Args&gt;
  34. Log(Args&amp;&amp;... args) -&gt; Log&lt;typename traits_argument&lt;Args...&gt;::type, typename format_argument&lt;Args...&gt;::type&gt;;

https://godbolt.org/z/EbrYheYPW

huangapple
  • 本文由 发表于 2023年7月7日 01:10:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/76631129.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定