gpt4 book ai didi

c++ - 使用 constexpr 进行基本编译时格式字符串检查

转载 作者:行者123 更新时间:2023-12-04 14:44:35 28 4
gpt4 key购买 nike

在我们的项目中,我们使用 printf 兼容函数将消息添加到外部日志文件。例如我们可以写

__LOG_INFO( "number of files = %d\n", number_of_files );
__LOG_INFO( "Just for information\n" );
__LOG_INFO 的函数声明看起来像这样
template<int N>
inline void __LOG_INFO( const char (&fmt)[N] )
{
call_printf( MODULE_NAME, fmt, debug_parameters() );
}

template<int N, typename T1>
static void __LOG_INFO( const char (&fmt)[N], const T1 &t1 )
{
call_printf( MODULE_NAME, fmt, debug_parameters( t1 ) );
}

template<int N, typename T1, typename T2>
static void __LOG_INFO( const char (&fmt)[N], const T1 &t1, const T2 &t2 )
{
call_printf( MODULE_NAME, fmt, debug_parameters( t1, t2 ) );
}

...

我们现在想使用 C++ 11 constexpr 功能添加一些简单的编译时格式字符串检查,例如要对格式字符串中的参数数量进行非常简单的检查,我们有这个函数
template<int N>
constexpr static int count_arguments( const char (&fmt)[N], int pos = 0, int num_arguments = 0 )
{
return pos >= N-2 ? num_arguments :
fmt[pos] == '%' && fmt[pos+1] != '%' ? count_arguments( fmt, pos+1, num_arguments+1 ) :
count_arguments( fmt, pos+1, num_arguments );
}

现在的问题是我们不能在 __LOG_INFO 中添加类似 static_assert 的东西。函数本身,因为编译器提示 fmt 不是一个整数常量。所以现在我们有这个丑陋的宏解决方案:
#define COUNT_ARGS(...) COUNT_ARGS_(,##__VA_ARGS__,8,7,6,5,4,3,2,1,0)
#define COUNT_ARGS_(z,a,b,c,d,e,f,g,h,cnt,...) cnt

#define LOG_INFO(a, ...) \
{ \
static_assert( count_arguments(a)==COUNT_ARGS(__VA_ARGS__), "wrong number of arguments in format string" ); \
__LOG_INFO(a,##__VA_ARGS__); \
}

所以不要调用 __LOG_INFO ,必须调用 LOG_INFO .

除了使用上面的那些宏之外,还有没有更好的解决方案?

最佳答案

我正在处理 compile-time format string library在此期间我遇到了类似的问题。所以我将在这里分享我的发现。

主要问题是,constexpr函数在 C++ 中定义为可在编译时和运行时调用。以下示例无效,因为 F必须能够从运行时上下文中调用。

/* Invalid constexpr function */
constexpr int F(int x) { static_assert(x == 42, ""); return x; }

/* Compile-time context */
static_assert(F(42) == 42, "");

/* Runtime context */
void G(int y) { F(y); } // The way F is defined, this can't ever be valid.

如果参数的类型是允许作为模板参数的类型,但解决方案很简单,我们只需通过模板参数传递即可。但如果不是,您可以将 constexpr 包装起来- 具有 lambda 的任意范围内的类中的表达式。
constexpr参数
/* Simply counts the number of xs, using C++14. */
static constexpr std::size_t count_xs(const char *fmt, std::size_t n) {
std::size_t result = 0;
for (std::size_t i = 0; i < n; ++i) {
if (fmt[i] == 'x') {
++result;
} // if
} // for
return result;
}

template <typename StrLiteral, typename... Args>
constexpr void F(StrLiteral fmt, Args &&... args) {
static_assert(count_xs(fmt, fmt.size()) == sizeof...(Args), "");
}

int main() {
F([]() {
class StrLiteral {
private:
static constexpr decltype(auto) get() { return "x:x:x"; }
public:
static constexpr const char *data() { return get(); }
static constexpr std::size_t size() { return sizeof(get()) - 1; }
constexpr operator const char *() { return data(); }
};
return StrLiteral();
}(), 0, 0, 0);
}

调用站点是荒谬的。尽管我讨厌宏,但我们可以用它来清理一下。
#define STR_LITERAL(str_literal) \
[]() { \
class StrLiteral { \
private: \
static constexpr decltype(auto) get() { return str_literal; } \
public: \
static constexpr const char *data() { return get(); } \
static constexpr std::size_t size() { return sizeof(get()) - 1; } \
constexpr operator const char *() { return data(); } \
}; \
return StrLiteral(); \
}()

int main() {
F(STR_LITERAL("x:x:x"), 0, 0, 0);
}

一般来说,我们可以使用这种包装 constexpr 的技术。 static 中的表达式 constexpr保存其 constexpr的功能-ness 通过函数参数。但请注意,这可能会终止编译时间,因为每次调用 F即使我们用等效的字符串调用它两次,也会导致不同的模板实例化。

略有改善

而不是为每次调用 F 实例化一个不同的模板,我们可以使它对于相同的格式字符串重用相同的实例化。
template <char... Cs>
class Format {
private:
static constexpr const char data_[] = {Cs..., '\0'};
public:
static constexpr const char *data() { return data_; }
static constexpr std::size_t size() { return sizeof(data_) - 1; }
constexpr operator const char *() { return data(); }
};

template <char... Cs>
constexpr const char Format<Cs...>::data_[];

template <char... Cs, typename... Args>
constexpr void F(Format<Cs...> fmt, Args &&... args) {
static_assert(count_xs(fmt, fmt.size()) == sizeof...(Args), "");
}

int main() {
F(Format<'x', ':', 'x', ':', 'x'>(), 0, 0, 0);
}

Welp,让我们使用另一个宏来制作 Format build “更好”。
template <typename StrLiteral, std::size_t... Is>
constexpr auto MakeFormat(StrLiteral str_literal,
std::index_sequence<Is...>) {
return Format<str_literal[Is]...>();
}

#define FMT(fmt) \
MakeFormat(STR_LITERAL(fmt), std::make_index_sequence<sizeof(fmt) - 1>())

int main() {
F(FMT("x:x:x"), 0, 0, 0);
}

希望这可以帮助!

关于c++ - 使用 constexpr 进行基本编译时格式字符串检查,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25218082/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com