字符串字面量,以及在C++23中的constexpr string msg = format(...)

编程语言 2026-07-08

我想在编译时使用format(...) 构造字符串字面量;
关于如何实现它,有些信息互相矛盾,我还做不到。

对于一个应用,我通常需要几个带有格式化的字符串字面量。

应 @Oersted的请求,我给出一个简要概括,说明我的需求。
通常的做法很简单,但属于“老派”,并且使用预处理器。我的问题是:“如何在字符串字面量中嵌入constexpr值,获得与旧式做法大致相同的灵活性”。欢迎任何“现代C++”的解决方案。

下面是更多说明,以及我在互联网上找到的一些消息,给出如何实现的代码,但示例我无法让GCC编译通过。

‘Old style’ solution.

#include <iostream>  

#define stringify(a) #a  
#define str(a) stringify(a)  

#define THE_NUMBER_DEFAULT 42  

int the_number = THE_NUMBER_DEFAULT;  
const char * the_message = "The default number is " str(THE_NUMBER_DEFAULT) ", good luck";  

int main(int argc, char **argv) {  
    std::cout << the_message << std::endl;  
    return 0;  
}

从C++11开始,以及 constexpr,这已不再被认为是“最佳实践”,但我从未找到一种把 constexpr 值组合到一个字符串字面量中的方法。
从C++23开始,应该有办法。
C++26正在到来,是时候向前迈进,改掉旧习惯。

关于如何用format() 创建字符串字面量,存在相互矛盾的信息。

this 告诉你这是不可能的。
this 1)this 告诉你如何做到。

这两个给出做法的示例都很简单。我按1) 的示例来做。
我使用Debian trixie + GCC + Cmake,都是最新版本。编译时启用了C++23。

#include <iostream>  
#include <string>  
#include <string_view>  

#include <format>  
#include <stacktrace>  

constexpr std::string_view prefix = "Hello, ";  
constexpr std::string_view name = "World!";  
constexpr std::string_view greeting = prefix.data() + name.data(); // Simple concatenation (careful with lifetimes)  

// Using std::string for compile-time concatenation (C++20 onwards)  
constexpr std::string full_message = std::string("The number is ") + std::to_string(123) + ".";  

int main(int argc, char **argv) {  
    static_assert(greeting.length() == 12);  
    static_assert(full_message.length() == 19);  
    std::cout << full_message << std::endl;  
    return 0;  
}

constexpr std::string_view greeting = prefix.data() + name.data(); 上编译失败,错误信息如下:

error: invalid operands of types ‘std::basic_string_view<char>::const_pointer’ {aka ‘const char*’} and ‘std::basic_string_view<char>::const_pointer’ {aka ‘const char*’} to binary ‘operator+’  
compilation terminated due to -Wfatal-errors.

我找到的这三条信息都没有告诉我使用的是哪种编译器。
后两条信息也没有给出任何编译命令或结果。

这让我产生了以下问题:

  • GCC是否支持这个?(我假设是的)
  • 假设GCC对 C++23的支持已经完善,我应该如何构建它?

欢迎任何关于如何实现的建议。

解决方案

你可以使用 std::to_chars,自C++23起它就是 constexprhttps://en.cppreference.com/cpp/utility/to_chars)。但这还不够。编译时的拼接并非一件容易的事。

它不能通过 std::string_view 实现,原因请参见那里:Concatenating string_view objects

它并不总是能与 std::string 一起工作,因为它会动态分配数据。因此只能在字符串在 constexpr 表达式结束时被销毁时使用。如果应用了短字符串优化,可能会偶然成立,但这取决于实现和数据。

所以问题比看起来要复杂一些。

首先,我将提出一个用户定义的编译时字符串字面量,并具有明确的拼接规则。

可以这样完成:

#include <string_view>

// LiteralString is a literal class type, usable as constant template parameter
// N does not includes null final character
template <std::size_t N>
struct LiteralString {
    consteval LiteralString(const char* s) { std::copy(s, s + N, &data[0]); }
    consteval operator std::string_view() const { return {data, N}; }

    static constexpr std::size_t size = N;
    char data[N]{};
};

// deduction guide for string literals
template <std::size_t N>
LiteralString(const char (&s)[N]) -> LiteralString<N - 1>;

// user-define string litteral
template <LiteralString LS>
constexpr auto operator""_ls() {
    return LS;
}

// compile-time only concatenation
template <std::size_t Ln, std::size_t Rn>
consteval auto operator+(LiteralString<Ln> const& lhs,
                         LiteralString<Rn> const& rhs) {
    constexpr std::size_t NewSize = Ln + Rn;
    char data[NewSize]{};
    auto ptr = std::copy(lhs.data, lhs.data + Ln, &data[0]);
    std::copy(rhs.data, rhs.data + Rn, ptr);
    return LiteralString<NewSize>{data};
}

请注意,稍后我需要从 const char* s 构造,这不允许从 N 推断。
如果我想从一个简单的字符串字面量初始化,例如 "Hello",我将需要根据字符串长度推断出 N,我通过推导指南实现这一点。

接下来用 std::to_chars 来实现从数字创建:

namespace Details {
// number of digits of i in base 10
template <unsigned int i>
consteval std::size_t NbDigits() {
    if constexpr (i <= 9) {
        return 1;
    } else {
        return NbDigits<i / 10>() + 1;
    }
}
}  // namespace Details

// compile-time creation of a string literal from an integer
template <unsigned int i>
consteval auto to_string() {
    // getting the needed size
    static constexpr std::size_t buf_size = Details::NbDigits<i>();
    char buf[buf_size]{};
    // using to_chars constexpr-ness
    std::to_chars(buf, buf + buf_size, i);
    return LiteralString<buf_size>(buf);
}

这相当直接。我只需要对缓冲区大小保持“聪明”一些。请注意,为了处理除了 unsigned int 之外的其他类型(处理符号、表示等),还需要做更多工作。

现在我们可以试试:

int main() {
    static constexpr auto full_message =
        "The number is "_ls + to_string<42>() + "."_ls;
    // casting to string_view for display, no need to overload operator <<
    std::cout << std::string_view{full_message};
}

输出:

数字是42。

现场演示

最后说明,作为额外内容,如有需要,可以将 LiteralString 作为模板参数使用。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章