Open rhalbersma opened 11 months ago
backup of removed comment on P3070R0
This proposal by @vitaut is great! In fact, if anything, the paper undersells itself. In my experience, there are considerable other benefits from overloading format_as
compared to specializing std::formatter
for user-defined types.
fmtlib
and the Standard Library with a convenient way to format their user-defined types currently need to provide specializations for both fmt::formatter
and std::formatter
. A single format_as
function overload (in the user-defined type's own namespace!) can be hooked into by both fmtlib
and the Standard Library (or any other formatting library build on top of them).format_as
function overload and the formatter
class specialization.fmtlib
sources mention this problem for std::vector<bool, Allocator>::reference
and std::bitset<N>::reference
and work around it by adding ad hoc constraints that try to infer "bitlikeness".namespace acme {
template<class T, class A>
struct container
{
struct proxy_reference
{
explicit(false) auto operator T() const; // implicitly convert to T
};
};
template<class T, class A>
auto format_as(container<T, A>::proxy_reference const & ref) // error: cannot deduce the template arguments
} // namespace acme
template<class T, class A>
struct std::formatter<acme::container<T, A>::proxy_reference> // error: cannot deduce the template arguments
Factoring the reference class outside its container will enable template argument deduction for both the format_as
overload and the formatter
specialization
namespace acme {
template<class T, class A>
struct container_reference
{
explicit(false) auto operator T() const; // implicitly convert to T
};
template<class T, class A>
struct container
{
using proxy_reference = container_reference<T, A>;
};
template<class T, class A>
auto format_as(container_reference<T, A> const& ref) // now compiles
{
return static_cast<T>(ref);
}
} // namespace acme
template<class T, class A, class CharT>
struct std::formatter<acme::container_reference<T, A>> // now compiles
: std::formatter<T, CharT>
{
template<class T, class A, class Context>
auto format(acme::container_reference<T, A> const& ref, Context& ctx)
{
return std::formatter<T, CharT>::format(static_cast<T>(ref), ctx);
}
}
However, a much less intrusive refactoring of acme::container
is possible by simply adding a friend
declaration for format_as
inside the container class that will be found by ADL
namespace acme {
template<class T, class A>
struct container
{
struct proxy_reference
{
explicit(false) auto operator T() const; // implicitly convert to T
};
friend auto format_as(proxy_reference const& ref) // will be found by ADL
{
return static_cast<T>(ref);
}
};
} // namespace acme
friend
format_as
overload for proxy references inside the container class.#include <https://raw.githubusercontent.com/rhalbersma/bit_set/master/include/xstd/bit_set.hpp>
#include <fmt/ranges.h>
int main()
{
// xstd::bit_set is a packed version of a std::set<int> container with proxy references/iterators
fmt::println("{}", xstd::bit_set<20>({2, 3, 5, 7, 11, 13, 17, 19}));
}
format_as
is considerably more flexible and general than specializing formatter
, especially for nested classes inside class templates. Please standardize such a customization point!
xstd::bit_set<N, Block>
is a bona fidestd::bidirectional_range
. We also provide aformat_as
overload forxstd::bit_set<N, Block>::proxy_reference
. This makes it currently printable withfmt::print
.If and when P3070R0 gets adopted, we will also support
std::print
.