Chris Morgan
Chris Morgan

Reputation: 1339

fmt formatter for private class type

How does one define a formatter for private class types that are inside of namespaces? Buidlable code at https://github.com/cmorganBE/fmttest. I have a number of classes like:

namespace example_namespace {

class someclass {
private:
        enum class someinternalvalue {
                these = 2,
                are = 9,
                examples = 10
        };

        someinternalvalue val;

public:
        someclass() : val(someinternalvalue::are){
        }

        void somefunction() {
                spdlog::info("{}", val);
        }
};

}

And a formatter outside of the namespace (because of compiler errors about fmt not being in the namespace):

template<>
struct fmt::formatter<example_namespace::someclass::someinternalvalue> : formatter<string_view> {
public:
    template <typename FmtContext>
    constexpr auto format(example_namespace::someclass::someinternalvalue const &t, FmtContext& ctx) const {
        const char* str;
        switch (t)
        {
            case example_namespace::someclass::someinternalvalue::these:
                str = "these";
                break;
            case example_namespace::someclass::someinternalvalue::are:
                str = "are";
                break;
            case example_namespace::someclass::someinternalvalue::examples:
                str = "examples";
            default:
                str = "unknown ValveStateComplex state";
                break;
        }

        return formatter<string_view>::format(str, ctx);
    }
};

Understandably this results in errors as the formatter is outside of the class:

home/cmorgan/projects/test/fmttest/main.cpp:53:15:   required from here
/home/cmorgan/projects/test/fmttest/main.cpp:67:18: error: ‘enum class example_namespace::someclass::someinternalvalue’ is private within this context
   67 |             case example_namespace::someclass::someinternalvalue::these:
      |                  ^~~~~~~~~~~~~~~~~
/home/cmorgan/projects/test/fmttest/main.cpp:40:20: note: declared private here
   40 |         enum class someinternalvalue {
      |                    ^~~~~~~~~~~~~~~~~
/home/cmorgan/projects/test/fmttest/main.cpp:70:18: error: ‘enum class example_namespace::someclass::someinternalvalue’ is private within this context
   70 |             case example_namespace::someclass::someinternalvalue::are:
      |                  ^~~~~~~~~~~~~~~~~
/home/cmorgan/projects/test/fmttest/main.cpp:40:20: note: declared private here
   40 |         enum class someinternalvalue {
      |                    ^~~~~~~~~~~~~~~~~
/home/cmorgan/projects/test/fmttest/main.cpp:73:18: error: ‘enum class example_namespace::someclass::someinternalvalue’ is private within this context
   73 |             case example_namespace::someclass::someinternalvalue::examples:
      |                  ^~~~~~~~~~~~~~~~~
/home/cmorgan/projects/test/fmttest/main.cpp:40:20: note: declared private here
   40 |         enum class someinternalvalue {
      |                    ^~~~~~~~~~~~~~~~~

But if you put the formatter inside of the class I get:

/home/cmorgan/projects/test/fmttest/main.cpp:55:10: error: explicit specialization in non-namespace scope ‘class example_namespace::someclass’
   55 | template<>
      |          ^
/home/cmorgan/projects/test/fmttest/main.cpp:56:13: error: declaration of ‘struct fmt::v9::formatter<example_namespace::someclass::someinternalvalue>’ in ‘class example_namespace::someclass’ which does not enclose ‘fmt’
   56 | struct fmt::formatter<example_namespace::someclass::someinternalvalue> : formatter<string_view> {
      |             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/spdlog/fmt/fmt.h:27,
                 from /usr/include/spdlog/common.h:45,
                 from /usr/include/spdlog/spdlog.h:12,
                 from /home/cmorgan/projects/test/fmttest/main.cpp:1:
/usr/include/fmt/core.h: In instantiation of ‘constexpr fmt::v9::detail::value<Context> fmt::v9::detail::make_value(T&&) [with Context = fmt::v9::basic_format_context<fmt::v9::appender, char>; T = example_namespace::someclass::someinternalvalue&]’:
/usr/include/fmt/core.h:1753:29:   required from ‘constexpr fmt::v9::detail::value<Context> fmt::v9::detail::make_arg(T&&) [with bool IS_PACKED = true; Context = fmt::v9::basic_format_context<fmt::v9::appender, char>; type <anonymous> = fmt::v9::detail::type::custom_type; T = example_namespace::someclass::someinternalvalue&; typename std::enable_if<IS_PACKED, int>::type <anonymous> = 0]’
/usr/include/fmt/core.h:1877:77:   required from ‘constexpr fmt::v9::format_arg_store<Context, Args>::format_arg_store(T&& ...) [with T = {example_namespace::someclass::someinternalvalue&}; Context = fmt::v9::basic_format_context<fmt::v9::appender, char>; Args = {example_namespace::someclass::someinternalvalue}]’
/usr/include/fmt/core.h:1894:38:   required from ‘constexpr fmt::v9::format_arg_store<Context, typename std::remove_cv<typename std::remove_reference<Args>::type>::type ...> fmt::v9::make_format_args(Args&& ...) [with Context = basic_format_context<appender, char>; Args = {example_namespace::someclass::someinternalvalue&}]’
/usr/include/spdlog/logger.h:370:68:   required from ‘void spdlog::logger::log_(spdlog::source_loc, spdlog::level::level_enum, spdlog::string_view_t, Args&& ...) [with Args = {example_namespace::someclass::someinternalvalue&}; spdlog::string_view_t = fmt::v9::basic_string_view<char>]’
/usr/include/spdlog/logger.h:90:13:   required from ‘void spdlog::logger::log(spdlog::source_loc, spdlog::level::level_enum, fmt::v9::format_string<T ...>, Args&& ...) [with Args = {example_namespace::someclass::someinternalvalue&}; fmt::v9::format_string<T ...> = fmt::v9::basic_format_string<char, example_namespace::someclass::someinternalvalue&>]’
/usr/include/spdlog/logger.h:96:12:   required from ‘void spdlog::logger::log(spdlog::level::level_enum, fmt::v9::format_string<T ...>, Args&& ...) [with Args = {example_namespace::someclass::someinternalvalue&}; fmt::v9::format_string<T ...> = fmt::v9::basic_format_string<char, example_namespace::someclass::someinternalvalue&>]’
/usr/include/spdlog/logger.h:158:12:   required from ‘void spdlog::logger::info(fmt::v9::format_string<T ...>, Args&& ...) [with Args = {example_namespace::someclass::someinternalvalue&}; fmt::v9::format_string<T ...> = fmt::v9::basic_format_string<char, example_namespace::someclass::someinternalvalue&>]’
/usr/include/spdlog/spdlog.h:157:31:   required from ‘void spdlog::info(fmt::v9::format_string<T ...>, Args&& ...) [with Args = {example_namespace::someclass::someinternalvalue&}; fmt::v9::format_string<T ...> = fmt::v9::basic_format_string<char, example_namespace::someclass::someinternalvalue&>]’
/home/cmorgan/projects/test/fmttest/main.cpp:53:15:   required from here
/usr/include/fmt/core.h:1733:7: error: static assertion failed: Cannot format an argument. To make type T formattable provide a formatter<T> specialization: https://fmt.dev/latest/api.html#udt
 1733 |       formattable,
      |       ^~~~~~~~~~~

What is the recommended approach here? I don't want to leak types out of classes by moving the enum classes both out of the classes and out of the namespaces.

Upvotes: 4

Views: 1285

Answers (1)

vitaut
vitaut

Reputation: 55595

You need to declare the formatter specialization a friend:

#include <fmt/format.h>

namespace example_namespace {

class someclass {
 private:
  enum class someinternalvalue {
    these = 2,
    are = 9,
    examples = 10
  };

  friend struct fmt::formatter<someinternalvalue>;
  // ...
};

}

template<>
struct fmt::formatter<example_namespace::someclass::someinternalvalue>
  : formatter<string_view> {
  // ...
};

https://godbolt.org/z/boqcPjfhz

Upvotes: 3

Related Questions