c++c++20format-stringstdformat

How do I preserve inter-string null characters in std::format string


In an attempt to refactor this code:

static const auto opf_str = [&]() {
    std::string result;
    for(auto& e : extension_list) {
        result.append(StringUtils::ToUpperCase(e.substr(1)) + " file (*"s + e + ")\0*"s + e + "\0"s);
    }
    result += "All Files (*.*)\0*.*\0\0"s;
    return result;
}();

I replaced the std::string::operator+ joins with std::format:

static const auto opf_str = [&]() {
    std::string result;
    for(auto e : extension_list) {
        result.append(std::format("{0} file (*{1})\0*{1}\0", StringUtils::ToUpperCase(e.substr(1)), e));
    }
    result += "All Files (*.*)\0*.*\0\0"s;
    return result;
}();

But the internal null characters (which are required for how OPENFILENAME works) are being stripped in the std::format version.

Changing the format string to an actual std::string causes a compiler error because the string isn't a compile-time constant.


Solution

  • std::string as argument doesn't work, but at least std::string_view should be fine. There is no issue with having it even as the result of a constant expression if it is referencing only static objects:

    std::format("{0} file (*{1})\0*{1}\0"sv, StringUtils::ToUpperCase(e.substr(1)), e)
    

    Looking a the current draft specification of std::basic_format_string it is indeed impossible to use a std::string as format string to std::format, even if its construction is a constant expression. As an alternative it is of course always possible to use std::vformat instead, which doesn't perform the compile-time checks. (It is in the end not really surprising since the consteval construction of the std::basic_format_string would need to copy the string contents, but can't rely on storing the result in dynamic memory.)