我正在设计一个日志记录器。我来格式化一下。我做了一个设计,我将预先确定格式字符串,然后将这些数据保存在一个元组中,并将其打印到日志中。
下面的代码现在可以工作了,但是我希望它能在我注释掉的部分工作。因此,根据我设置的默认参数,如果没有给出参数,我希望它使用默认参数。
注意:我希望这些进程是在编译时。
#include <iostream>
#include <tuple>
#include <memory>
#include <type_traits>
class MyLogger {
public:
template<typename... Args>
void Log(const char* format, Args... args) {
printf(format, args...);
printf("n");
}
};
static inline std::unique_ptr<MyLogger> logger = std::make_unique<MyLogger>();
template<typename FMT, typename... Args>
struct LogLineDef {
LogLineDef(Args... args) : values(args...) {}
std::tuple<Args...> values;
};
constexpr char FMT1[] = "A:%d, B:%d, C:%d, D:%f ";
using L1 = LogLineDef<std::integral_constant<const char*, FMT1>, int, int, int, float>;
template<typename FMT, typename... Args>
void log_helper(const char* message, const LogLineDef<FMT, Args...>& logger_line_def) {
std::apply([message](const auto&... args) {
logger->Log((FMT::value + std::string(message)).c_str(), args...);
}, logger_line_def.values);
}
#define LOG(logger_line_def, message, ...) log_helper(message, logger_line_def, ##__VA_ARGS__)
int main() {
LOG(L1(1, 2, 3, 4.f), "error");
//LOG(L1(1, 2, 3), "error"); // should be legal according to default value
//LOG(L1(1, 2), "error"); // should be legal according to default value
}
我实现了一个解决方案,您可以通过以下方式定义类型列表和标签:
static constexpr auto L1 = LabelList<int, int, int, float>{"A", "B", "C", "D"};
如果您提供较少的参数,则不打印它们。如果提供的参数有其他类型,那么在定义中,您将得到编译时错误。您需要为您想要支持的每种类型定义一个c_str
格式化程序:
template <>
inline constexpr char const* c_format<int> = "%d";
下面是完整的日志记录:
#include <cstdio>
#include <memory>
#include <sstream>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
class MyLogger {
public:
void Log(std::string const& data, std::string const& message) {
std::printf("%s %sn", data.c_str(), message.c_str());
}
};
static inline std::unique_ptr<MyLogger> logger = std::make_unique<MyLogger>();
template <typename T>
inline constexpr char const* c_format = "c_format missing";
template <>
inline constexpr char const* c_format<int> = "%d";
template <>
inline constexpr char const* c_format<float> = "%f";
template <typename T>
struct Label: std::string_view {
using std::string_view::string_view;
using type = T;
};
template <typename ... Ts, std::size_t ... Is, typename U, typename ... Us>
std::string to_string(
std::tuple<Label<Ts> ...> const& labels,
std::index_sequence<Is ...>,
U&& first, Us&& ... values
) {
static_assert(
std::is_same_v<typename std::remove_reference_t<decltype(std::get<0>(labels))>::type, U> &&
(std::is_same_v<typename std::remove_reference_t<decltype(std::get<Is + 1>(labels))>::type, Us> && ...));
using namespace std;
std::ostringstream format;
format << std::get<0>(labels) << ": " << c_format<U>;
((format << ", " << std::get<Is + 1>(labels) << ": " << c_format<Us>), ...);
auto const c_format = std::move(format).str();
auto const lenght =std::snprintf(nullptr, 0, c_format.c_str(), first, values ...);
std::string result(lenght, ' ');
sprintf(result.data(), c_format.c_str(), first, values ...);
return result;
}
template <typename ... Ts>
struct LabelList: std::tuple<Label<Ts> ...> {
using std::tuple<Label<Ts> ...>::tuple;
template <typename ... Us>
std::string operator()(Us&& ... values) const {
static_assert(sizeof...(Us) <= sizeof...(Ts));
if constexpr (sizeof...(Us) == 0) {
return "";
} else {
return to_string(*this,
std::make_index_sequence<sizeof...(Us) - 1>(),
std::forward<Us>(values) ...);
}
}
};
static constexpr auto L1 = LabelList<int, int, int, float>{"A", "B", "C", "D"};
int main() {
logger->Log(L1(1, 2, 3, 4.f), "error");
logger->Log(L1(1, 2, 3), "error");
logger->Log(L1(1, 2), "error");
}
A: 1, B: 2, C: 3, D: 4.000000 error
A: 1, B: 2, C: 3 error
A: 1, B: 2 error