英文:
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);
它接受:
- 任意数量的 "日志特性"(
Error
、If
、Title
),在运行时进行评估 - 格式化字符串 + 任意数量的参数作为消息,使用
std::format_to
进行格式化
问题是,我想使用 std::format_to
进行其编译时格式检查,这要求字符串 "Failed to execute task, ID: {}""
必须保持为 consteval
,以便将其转换为 std::format_string<...>
,但我不希望每个 "日志特性" 也都是 consteval
,因为有些需要在运行时评估,比如 If
。
我的当前尝试是定义一个 LogTrait
概念,所有 Error
、If
和 Title
都遵循:
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:
- Any number of "Log Traits" (
Error
,If
,Title
), to be evaluated at runtime - 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("Operation failed")
("Failed to execute task, ID: {}", taskId);
Log(
Error,
If(result == false),
Title("Operation failed")
)("Failed to execute task, ID: {}",
taskId);
// Or make a new "LogTrait" to hold the format string with it's args
Log(
Error,
If(result == false),
Title("Operation {} failed", name), // This should also be a format string
Body("Failed to execute task, ID: {}", taskId));
It is possible, albeit difficult, to maintain your current syntax. Once you've passed "Failed to execute task, ID: {}"
as a const char(&)[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<LogTrait... Traits, typename... Args>
void do_log(std::tuple<Traits...> traits, std::format_string<Args...> fmt, Args&&... args) {
// ...
}
You can implement Log
like this:
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>{}));
// Index of the first non-LogTrait argument, the format string
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>;
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论