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

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

Splitting consteval from non-consteval expressions in parameter packs

问题

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

Log(
    Error,
    If(result == false),
    Title("Operation failed"),
    "Failed to execute task, ID: {}",
    taskId);

它接受:

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

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

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

template<typename T> concept LogTrait = ...

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

template<typename... Args>
consteval auto GetFormatString(std::format_string<Args...> fmt, Args&&... args)
{
	return std::make_pair(fmt, std::make_format_args(std::forward<Args>(args)...));
}

template<typename... Args>
constexpr auto GetFormatString(LogTrait auto&&, Args&&... args)
{
	return GetFormatString(std::forward<Args>(args)...);
}

但如果我将这两个函数都设为 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:

Log(
    Error,
    If(result == false),
    Title("Operation failed"),
    "Failed to execute task, ID: {}",
    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:

template<typename T> concept LogTrait = ...

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

template<typename... Args>
consteval auto GetFormatString(std::format_string<Args...> fmt, Args&&... args)
{
	return std::make_pair(fmt, std::make_format_args(std::forward<Args>(args)...));
}

template<typename... Args>
constexpr auto GetFormatString(LogTrait auto&&, Args&&... args)
{
	return GetFormatString(std::forward<Args>(args)...);
}

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

以下是翻译好的部分:

最简单的方法是通过更改调用`Log`函数的方式来绕过此问题:

```c++
Log
    .Error
    .If(result == false)
    .Title("Operation failed")
    ("Failed to execute task, ID: {}", taskId);


Log(
    Error,
    If(result == false),
    Title("Operation failed")
)("Failed to execute task, ID: {}",
    taskId);

// 或者创建一个新的“LogTrait”来保存格式字符串及其参数
Log(
    Error,
    If(result == false),
    Title("Operation {} failed", name),  // 这也应该是一个格式字符串
    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可以具有正确的类型。

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

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

您可以像这样实现Log

template<typename Traits, typename Args>
struct Log;

template<typename... Traits, typename... Args>
struct Log<std::tuple<Traits...>, std::tuple<Args...>>{
    Log(const Log&) = delete;
    Log& operator=(const Log&) = delete;
    ~Log() = default;

    Log(Traits&&... traits, std::format_string<Args...> fmt, Args&&... args) {
        do_log(std::forward_as_tuple(std::forward<Traits>(traits)...), fmt, std::forward<Args>(args)...);
    }
};

template<std::size_t Offset, typename... Args, std::size_t... I>
auto tuple_index(std::index_sequence<I...>) -> std::tuple<std::tuple_element_t<Offset + I, std::tuple<Args...>>...>;

template<std::size_t N, typename... Args>
using tuple_of_first = decltype(tuple_index<0, Args...>(std::make_index_sequence<N>{}));
template<std::size_t N, typename... Args>
using tuple_of_last = decltype(tuple_index<sizeof...(Args)-N, Args...>(std::make_index_sequence<N>{}));

// 第一个非LogTrait参数的索引,即格式字符串
template<typename... Args>
inline constexpr std::size_t format_string_index = []<std::size_t... I>(std::index_sequence<I...>) {
    constexpr std::size_t format_string_index = std::min({ LogTrait<Args> ? std::size_t(-1) : I... });
    static_assert(format_string_index != std::size_t(-1), "Log(...): No format string");
    return format_string_index;
}(std::index_sequence_for<Args...>{});

template<typename... Args>
struct traits_argument {
    using type = tuple_of_first<format_string_index<Args...>, Args...>;
};

template<typename... Args>
struct format_argument {
    using type = tuple_of_last<sizeof...(Args) - 1u - format_string_index<Args...>, Args...>;
};

template<typename... Args>
Log(Args&&... args) -> Log<typename traits_argument<Args...>::type, typename format_argument<Args...>::type>;

https://godbolt.org/z/EbrYheYPW


<details>
<summary>英文:</summary>
The easiest way would be to sidestep this by changing how you call the `Log` function:
```c++
Log
.Error
.If(result == false)
.Title(&quot;Operation failed&quot;)
(&quot;Failed to execute task, ID: {}&quot;, taskId);
Log(
Error,
If(result == false),
Title(&quot;Operation failed&quot;)
)(&quot;Failed to execute task, ID: {}&quot;,
taskId);
// Or make a new &quot;LogTrait&quot; to hold the format string with it&#39;s args
Log(
Error,
If(result == false),
Title(&quot;Operation {} failed&quot;, name),  // This should also be a format string
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:

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

You can implement Log like this:

template&lt;typename Traits, typename Args&gt;
struct Log;

template&lt;typename... Traits, typename... Args&gt;
struct Log&lt;std::tuple&lt;Traits...&gt;, std::tuple&lt;Args...&gt;&gt;{
    Log(const Log&amp;) = delete;
    Log&amp; operator=(const Log&amp;) = delete;
    ~Log() = default;

    Log(Traits&amp;&amp;... traits, std::format_string&lt;Args...&gt; fmt, Args&amp;&amp;... args) {
        do_log(std::forward_as_tuple(std::forward&lt;Traits&gt;(traits)...), fmt, std::forward&lt;Args&gt;(args)...);
    }
};

template&lt;std::size_t Offset, typename... Args, std::size_t... I&gt;
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;;

template&lt;std::size_t N, typename... Args&gt;
using tuple_of_first = decltype(tuple_index&lt;0, Args...&gt;(std::make_index_sequence&lt;N&gt;{}));
template&lt;std::size_t N, typename... Args&gt;
using tuple_of_last = decltype(tuple_index&lt;sizeof...(Args)-N, Args...&gt;(std::make_index_sequence&lt;N&gt;{}));

// Index of the first non-LogTrait argument, the format string
template&lt;typename... Args&gt;
inline constexpr std::size_t format_string_index = []&lt;std::size_t... I&gt;(std::index_sequence&lt;I...&gt;) {
    constexpr std::size_t format_string_index = std::min({ LogTrait&lt;Args&gt; ? std::size_t(-1) : I... });
    static_assert(format_string_index != std::size_t(-1), &quot;Log(...): No format string&quot;);
    return format_string_index;
}(std::index_sequence_for&lt;Args...&gt;{});

template&lt;typename... Args&gt;
struct traits_argument {
    using type = tuple_of_first&lt;format_string_index&lt;Args...&gt;, Args...&gt;;
};

template&lt;typename... Args&gt;
struct format_argument {
    using type = tuple_of_last&lt;sizeof...(Args) - 1u - format_string_index&lt;Args...&gt;, Args...&gt;;
};

template&lt;typename... Args&gt;
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:

确定