Why does std::format and std::vformat take forwarding references for its arguments?

215 views Asked by At

I needed to use std::format in a context like this at work:

for(std::size_t i = 0; i < ...; ++i)
{
   std::format("some string {}", i);
}

and since the cycle variable i is passed as a forwarding reference, the automated linter gave a warning that it's passed by reference and could potentially be modified.

After reading through cppreference and the std::format proposal it seems that the Formatter requirement allows the formatted argument to be non-const, but I couldn't figure out why, and the draft actually suggests const Args&... parametrization instead of Args&&...

As an experiment I tried moving a string, or even specializing std::formatterfor my own type:

#include <format>
#include <iostream>
#include <string>

struct MyStruct {
    std::string a = "abc";

    MyStruct(){}
    MyStruct(const MyStruct&) { std::cout << "copy ctor" << std::endl; }
    MyStruct(MyStruct&&) noexcept { std::cout << "move ctor" << std::endl; }
    MyStruct& operator=(const MyStruct&) { std::cout << "copy assignment" << std::endl; return *this; }
    MyStruct& operator=(MyStruct&&) noexcept { std::cout << "move assignment" << std::endl; return *this; }
};

template <typename CharT>
struct std::formatter<MyStruct, CharT> : std::formatter<std::string, CharT> {
    template<typename FormatContext>
    auto format(MyStruct id, FormatContext& ctx) const {
        return std::formatter<std::string, CharT>::format(id.a, ctx);
    }
};

int main()
{
    std::string a = std::format("formatted {}", MyStruct{});
    return 0;
}

But even when declaring format with an lvalue parameter instead of a const&, only copy ctor gets called (std::vformat with std::make_format_args(MyStruct{}) results in the same).

So the final question is, why does std::format take forwarding reference arguments, why not restrict them to const&?

1

There are 1 answers

0
Brian Bi On BEST ANSWER

P2418 added support for formatters that can mutate the value being formatted. As the paper explains, the original motivation was for std::generator and similar types. Note, however, that forwarding references are present only on the entry points where arguments are first passed into the formatting API (std::format, std::vformat, etc); the more internal machinery (such as std::basic_format_arg) only deals with lvalue references, which may be to either a const or non-const type.