Search code examples
c++c++20fmt

Why C++20 format has no strong typedef for format string?


C++20 introduces the following format function (locale and wstring_view versions ignored since they do not affect the question):

template<class... Args>
std::string format(std::string_view fmt, const Args&... args);

There is nothing wrong with this, but I wonder why is there not an overload that accepts a "strong typedef", something like

template<class... Args>
    std::string format(std::format_string fmt, const Args&... args);

My guesses would be some or all of the following:

  1. increased complexity of implementation

  2. increased compilation time

  3. code bloat

, but I wonder if this was ever discussed during standardization.


Solution

  • The point of strong typedefs is to prevent this from working:

    void takes_id(SomeIdType);
    takes_id(42); 
    

    The point of format is to allow this to work:

    format("User {} owes me {} points.", name, 100);
    

    That is a string literal. Requiring a strong type means more burden on the users, having to write something like this:

    format(format_string("User {} owes me {} points."), name, 100);
    

    This isn't a burden on the typical strong typedef use case, since you will actually be trafficking in SomeIdTypes. You'll have a function that gives you a SomeIdType, you'll store a member of type SomeIdType. Basically, the amount of actual conversions will be fairly minimal... so on the call site you would just write takes_id(my_id) and the code mostly just looks the same, with added safety.

    But the overwhelmingly common case for formatting is to use string literals, so that's a lot of added annotation.

    The nominal benefit of strong typing is to catch users doing maybe something like this:

    format(name, "User {} owes me {} points.", 100);
    

    Or even:

    format(name, 100);
    

    The former seems unlikely to ever happen. The latter is certainly possible, if the first argument happens to be sufficiently string-like. But is this a sufficiently common problem as to force everyone to write more code? I don't think so.

    Now, if string literals had their own distinct type from const char[N] (and I really wish they did), then it would be possible to create a type that is implicitly constructible from std::string_literal but needs to be explicitly constructed from std::string_view. And if that were a thing, then the API probably would've used that - since this would require no annotation in the common case and not using string literals seems sufficiently rare that requiring an explicit cast seems... fine?

    Besides, on the question of safety, the issue isn't so much passing the wrong kind of string as actually being able to verify the value of it in its context:

    format("User {} owes me {} points.", name);
    

    We'd really like for this not to compile, even though we provided a format string in the correct spot. And it appears to be possible to do this. But we don't need strong typedefs for this either, we just need the ability to know if the format string is a constant expression or not.


    To summarize, the answer to:

    but I wonder why is there not an overload that accepts a "strong typedef"

    is that this requires users to provide more call-side annotation while providing very minimal benefit. It would only catch wrong uses in the rarest of uses, so seems like a fairly bad trade-off.