Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make bit_set compatible with std::print #20

Open
rhalbersma opened this issue Dec 31, 2023 · 1 comment
Open

Make bit_set compatible with std::print #20

rhalbersma opened this issue Dec 31, 2023 · 1 comment
Assignees
Labels
enhancement New feature or request

Comments

@rhalbersma
Copy link
Owner

rhalbersma commented Dec 31, 2023

xstd::bit_set<N, Block> is a bona fide std::bidirectional_range. We also provide a format_as overload for xstd::bit_set<N, Block>::proxy_reference. This makes it currently printable with fmt::print.

If and when P3070R0 gets adopted, we will also support std::print.

@rhalbersma rhalbersma added the enhancement New feature or request label Dec 31, 2023
@rhalbersma rhalbersma self-assigned this Dec 31, 2023
@rhalbersma
Copy link
Owner Author

rhalbersma commented Jan 5, 2024

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.

  1. Users wanting to provide both 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).
  2. Providing the correct formatting for nested classes inside class templates is a pain since the template parameters cannot be deduced. This applies to both the format_as function overload and the formatter class specialization.
  3. Case in point: the 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".
  4. Below a worked example of the general problem and ways around it:
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
  1. I have a fully worked bitset container with a nested proxy reference class available on GitHub and Compiler Explorer that adds just such a 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}));
}
  1. TLDR: overloading format_as is considerably more flexible and general than specializing formatter, especially for nested classes inside class templates. Please standardize such a customization point!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant