cppfront icon indicating copy to clipboard operation
cppfront copied to clipboard

[BUG] Expansion of string literal with interpolation results in invalid std::format_string arg

Open alexriegler opened this issue 1 year ago • 2 comments

Describe the bug

When string interpolation is used within a string literal that is used to construct a std::format_string (for example, in std::print or std::format), the string literal is expanded to a non-compile-time constant. The constructor of std::format_string is consteval so it requires that the argument is a compile-time constant. This results in a compile-time error when compiling the resulting C++ code.

This issue was encountered with cppfront compiler v0.7.2 Build 9809:1046 and MSVC 19.42.34226.3.

To Reproduce

Here is an example of cpp2 code that illustrates this issue:

// main.cpp2
main: () = {
    msg := "world";
    std::print("Hello, (msg)$!\n");
}

After running cppfront main.cpp2 -p, the output is:

// main.cpp
#define CPP2_IMPORT_STD          Yes

//=== Cpp2 type declarations ====================================================


#include "cpp2util.h"

#line 1 "main.cpp2"


//=== Cpp2 type definitions and function declarations ===========================

#line 1 "main.cpp2"
auto main() -> int;

//=== Cpp2 function definitions =================================================

#line 1 "main.cpp2"
auto main() -> int{
#line 2 "main.cpp2"
    auto msg {"world"}; 
    std::print("Hello, " + cpp2::to_string(cpp2::move(msg)) + "!\n");
}

If I attempt to compile the output code with:

cl /Femain /std:c++latest /EHsc /I $env:CPPFRONT_INCLUDE "$env:VCToolsInstallDir\modules\std.ixx" "$env:VCToolsInstallDir\modules\std.compat.ixx" main.cpp

I get the following compiler errors:

main.cpp2(3): error C7595: 'std::basic_format_string<char>::basic_format_string': call to immediate function is not a constant expression
main.cpp2(3): note: failure was caused by call of undefined function or one not declared 'constexpr'
main.cpp2(3): note: see usage of 'cpp2::to_string'
main.cpp2(3): note: the call stack of the evaluation (the oldest call first) is
main.cpp2(3): note: while evaluating function 'std::basic_string<char,std::char_traits<char>,std::allocator<char>> cpp2::to_string<const char*>(const _T0 &)'
        with
        [
            _T0=const char *
        ]

The issue seems to be that "Hello, " + cpp2::to_string(cpp2::move(msg)) + "!\n" is not a compile-time expression.

If it's not possible to make cpp2::to_string a constant evaluated function, then I would expect that the cpp2 code std::print("Hello, (msg)$!\n"); to compile to C++ code similar to std::print("Hello, {}!\n", msg);. I think this could potentially solve the issue.

alexriegler avatar Aug 17 '24 21:08 alexriegler

Thanks for the feedback!

Yes, C++23 std::print takes a std::basic_format_string which only has a consteval constructor in C++23. So my understanding is that as of C++23 std::print is designed to be used with pretty much just string literals. It doesn't work with std::string objects I think(? even though those can be constexpr now?), which means it can't be used with Cpp2 interpolated strings... or with Cpp1 std::format (or any other way I know of to create such a string today, but see (1) below):

// As of C++23
main: () = {
    msg := 42;
    std::print( "Hello, {}!\n", msg );                    // A: ok
    //std::print( std::format("Hello {}!\n", "world") );  // B: error
    //std::print( "Hello, (msg)$!\n" );                   // C: error
}

So I see two possible directions to make it work:

  1. Request for information: Does anyone know a way in C++23 to generate a consteval-compatible object that can be passed to std::print for case B or C above, say that converts an integer to a stringish thing and prepends/appends something and the resulting stringish things is consteval-friendly? (If the answer is no, we probably should relabel this issue from "bug" to something else because it is a limitation but I don't think a bug?)

  2. Otherwise, C++26 adds a runtime format string constructor to basic_format_string that can take a runtime-format-string generated by C++26 runtime_format. So maybe once runtime_format starts to become available I could maybe update interpolation to lower to that.

  3. Otherwise, I could try to heroically support std::print as a special case to lower interpolation differently there, but it would be a complication specifically for one function (that has only started to become available, it appeared just this year in GCC and Clang), so it's probably not worth special-casing if there's a general solution available now in (1) or on the horizon in (2).

I'd love to know about a way to do (1). The most likely answer may be (2)? And I'm loath to do special-purpose work for just std::print like (3) unless we know (1) and (2) are non-starters.

hsutter avatar Aug 18 '24 01:08 hsutter

both B and C would work by assigning to a variable first and calling std::print("{}", var); not idea how involved such a change would be in cppfront we could also use a immediately invoked lambda expression to avoid polluting with variable names

@alexriegler I don't think it's worth using std::print if you're using cpp2's interpolation anyway and not std::print's facilities for formatting to @hsutter fmt::format and fmt::print have been available for quite a while longer and are still significantly faster and more feature rich than std::print... maybe we could do a flag and allow doing interpolation with std::print or fmt::print instead for those who care about performance in their printing/string formatting if someone is willing to do the work

farmerpiki avatar Aug 18 '24 20:08 farmerpiki