我希望在代码中消除对#define宏的依赖,但我无法使用constexpr
实现相同的功能。
实际上,考虑以下示例:
#define PRODUCT_NAME "CloysterHPC"
constexpr const char* productName = PRODUCT_NAME;
class Newt : public View {
private:
struct TUIText {
#if __cpp_lib_constexpr_string >= 201907L
static constexpr const char* title =
fmt::format("{} Installer", productName).data();
#else
static constexpr const char* title = PRODUCT_NAME " Installer";
#endif
};
};
我很难理解fmt::format()
函数不是constexpr
函数,它只是一个运行时函数。我希望我可以在代码中使用它,使其更具表现力,但我做不到。所以我尝试使用std::string
,但在将代码更改为以下内容后,我再次得到了相同的结果:
#define PRODUCT_NAME "CloysterHPC"
constexpr const char* productName = PRODUCT_NAME;
class Newt : public View {
private:
struct TUIText {
#if __cpp_lib_constexpr_string >= 201907L
static constexpr const char* title = std::string{
std::string{productName} + std::string{" Installer"}}.data();
#else
static constexpr const char* title = PRODUCT_NAME " Installer";
#endif
};
};
那么,我的误解是什么呢?
1.我可以在constexpr
上下文中使用fmt
,这是不正确的。
std::string
在libstdc++
的适当支持下应该是constexpr
,以便在编译时计算字符串操作,但情况似乎并非如此。
1.我误解了标准库上__cpp_lib_constexpr_string
宏的实用程序。- C++20将在
constexpr
上下文中提供更大的文本操作灵活性。
我已经做了功课,遇到了其他问题os StackOverflow关于类似的问题,或者如何在constexpr
上下文中使用std::string
:
- Difference between
constexpr
and#define
- constexpr std::string in C++20, how does it work?
- Is it possible to use std::string in a constexpr?
但他们都没有明确回答我的问题:* 如何在编译时连接两个给定的字符串以正确地去除代码中的#define
宏?*
这看起来微不足道,因为两个字符串在编译时都是已知的,而且它们也是constexpr
,最终目标是有第三个constexpr const char*
,其内容如下:CloysterHPC Installer
,而不对代码使用任何#define
宏。
如何实现这一点?在当前的语言状态下这是可能的吗?我应该继续使用宏吗?我当前的设置是GCC 12.1.1,在RHEL 8.7系统上使用默认的libstdc++
:
gcc-toolset-12-12.0-5.el8.x86_64
gcc-toolset-12-libstdc++-devel-12.1.1-3.4.el8_7.x86_64
谢谢。
PS:请注意,有时我在问题中提到字符串时知道它们实际上不是std::string
,而是const char*
,这只是一个语言约定,而不是类型定义。
3条答案
按热度按时间aiazj4mn1#
您可以使用
FMT_COMPILE
在编译时格式化std::array
。Demo
vsmadaxz2#
我可以在constexprcontext中使用fmt。这是不正确的。
是的,我实际上不确定为什么会这样。也许有一些问题会使它目前很难实现(Eidogg.在实现中依赖一些非
constexpr
函数)。但即使它是
constexpr
,在这里也没有用,因为它返回std::string
和以下几点:在libstdc 的适当支持下,std::string应该是constexpr,以便在编译时计算字符串操作,但实际情况似乎并非如此。
Libstdc 确实支持C20
constexpr
友好的std::string
,并且可以在编译时操作它们。这里没有实现问题。但是,目前语言中没有机制让编译时的动态分配持续到运行时。但是std::string
确实需要动态分配,因为它可以存储任意长度的字符串,所以不可能将std::string
从编译时上下文传递到运行时上下文(例如,使用constexpr
关键字定义std::string
)。编译时使用的任何std::string
必须在编译时上下文结束之前销毁。我误解了标准库中__cpp_lib_constexpr_string宏的实用程序。
它只表明
std::string
可以在编译时使用,正如我上面所描述的。C20将在constexpr上下文中提供更大的文本操作灵活性。
从C++20开始,在
constexpr
上下文中,你可以自由地以任何你想要的方式操作std::string
。但是,你不仅试图在编译时上下文中操作std::string
,你还试图跨越编译时/运行时边界传递std::string
。但他们都没有明确回答我的问题:* 如何在编译时连接两个给定的字符串以正确地去除代码中的#define宏?*
正如我前面所描述的,基本问题是
std::string
需要动态分配来提供任意长度的字符串,因此它不能用于此目的,也不能仅使用const char*
,因为即使您在编译时计算常量,它仍然是一个有生命周期的对象,原始指针不能管理对象的生命周期。(字符串常量是一个例外,因为语言只需要在代码中编写就可以为它们提供生存期。)因此,要解决您的问题,您需要一个类型,它可以拥有并管理字符串内容的生存期,并且不需要动态内存分配,这意味着它必须存储固定长度的字符串,例如:
这里我将把
N
解释为包含空终止符的空间。这里的字符串长度是类型的一部分,所以我们不能简单地编写一个函数,把输入作为函数参数,而不把长度编码在它们的类型中;或者对于更一般的字符串操作,输出字符串的长度可能取决于字符串输入的内容。
因此,我们需要确保输入以其类型编码字符串的方式传递,其中一种方法是作为模板参数传递。
现在你可以写例如下面的函数:
其可以用
fixed_string
作为模板自变量来调用。现在剩下的问题是将字符串作为模板参数传递,这是不允许的,也就是说模板参数不能是
const char*
类型,我们需要一种方法来将字符串转换为fixed_string
,但这并不太困难。事实上,这就是库函数std::to_array
所做的(假设我们定义了fixed_string):现在
title.data()
可以像原来的title
一样使用(如果需要,可以存储一个constexpr const char*
,但是需要将fixed_string
存储在一个实际的constexpr
变量中,以管理数据的生命周期)。所有这些都可以通过使用字符串和范围语义定义自己的
fixed_string
类来改进,而不是依赖于std::array
。(作为对const char
数组的引用)。通过匹配的推导指南,auto
模板参数也可以替换为fixed_string
。这样CTAD就可以直接接受字符串作为模板参数,而不是通过std::to_array
等传递。上面的实现是最小的,还有很多可以做得更干净。此外,可以使用
std::integral_constant
或类似的模板和字符串文字操作符从模板参数移动到函数参数。另外,在
concat_constant_strings
的实现中,你可以先从str1
和str2
生成std::string
,然后生成一个新的字符串,最后构造一个fixed_string
返回,这样你就可以提升任何常规的std::string
操作:wgx48brx3#
您编写的“连接字符串”代码是一个即将发生的访问冲突:
所有在那里构建的临时字符串都会在该行的末尾过期,而留下的指针将指向未分配的内存,再次出现访问冲突。
现在来看看你的连接,
operator+
在string
和string
或者一个常量上确实有效,它确实是constexpr
,如果你看一下连接两个constexpr string
对象的编译器输出,你会发现它正确地连接了它们:你不能做的是“泄漏”动态内存分配(例如,通过将结果赋给变量)。C++20
constexpr
的方式是,动态分配在函数内部工作(在本例中,在operator+
内部),但它们不能泄漏到外部,它们必须在函数内部收集和释放。我们需要更多的支持来尝试做什么。