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::formatter
for 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&
?
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 asstd::basic_format_arg
) only deals with lvalue references, which may be to either aconst
or non-const
type.