c++c++20fmt

Is there a problem with fmt format_as in C++20?


I've got a simple code example that attempts to use a format_as() overload to format a custom type (an enum); it outputs a std::string found in a map.

From the link above, the compiler is configured for C++20 and the code fails to compile, telling me it cannot format the argument to fmt::print(). Switching to C++17, it compiles and runs correctly with no other changes.

From the compiler error it seems like the compiler just isn't seeing my format_as() under C++20, but the error highlight in the editor makes me think the actual problem might be a little different. I'm not really making sense of that message or the output from the compiler though.

I believe I'm following the rules, as the successful build in C++17 seems to confirm. What am I missing?

EDIT: With apologies to the downvoters for not posting the code:

// Type your code here, or load an example.
#include <fmt/format.h>
#include <map>
#include <cassert>

template <typename K, typename V>
std::map<V, K> create_reverse_map(const std::map<K, V>& input_map);

typedef enum {
    RT_COIL = 0,
    RT_INPUT = 1,
    RT_HOLDING = 2
} regtype_t;
auto format_as(regtype_t rt);

//Make a map associating strings with enum values,
//and a reverse map we'll generate at runtime
static std::map<std::string, regtype_t> regtypes = {
    {"COIL", RT_COIL},
    {"INPUT", RT_INPUT},
    {"HOLDING", RT_HOLDING}
};
static std::map<regtype_t, std::string> regtypes_r;

int main(void) {
    //create a reverse of our lookup map, for diagnostic message output
    regtypes_r = create_reverse_map(regtypes);
    
    //Try format of a regtype_t enum, pulled from the lookup map
    //Should get us the same string we're using for the key here
    fmt::print("Testing: '{0}'\n", regtypes["HOLDING"]);
    return 0;
}

// format_as() overload for our enum
// tack on some asterisks so we can see we've been here
auto format_as(regtype_t rt) { return std::string("*** ") + regtypes_r[rt]; }

// reverse map generator
template <typename K, typename V>
std::map<V, K> create_reverse_map(const std::map<K, V>& input_map) {
    std::map<V, K> reverse_map;
    for (const auto& pair : input_map) {
        // both keys and values must be unique
        assert (reverse_map.find(pair.second) == reverse_map.end());
        reverse_map[pair.second] = pair.first;
    }
    return reverse_map;
}

The compile error in C++20:

In file included from /opt/compiler-explorer/libs/fmt/11.0.0/include/fmt/format.h:41,
                 from <source>:2:
/opt/compiler-explorer/libs/fmt/11.0.0/include/fmt/base.h: In instantiation of 'constexpr decltype (ctx.begin()) fmt::v10::detail::parse_format_specs(ParseContext&) [with T = regtype_t; ParseContext = compile_parse_context<char>; decltype (ctx.begin()) = const char*]':
/opt/compiler-explorer/libs/fmt/11.0.0/include/fmt/base.h:2747:51:   required from here
 2747 |     return id >= 0 && id < num_args ? parse_funcs_[id](context_) : begin;
      |                                       ~~~~~~~~~~~~^
<source>:31:16:   in 'constexpr' expansion of 'fmt::v10::basic_format_string<char, regtype_t&>("Testing: \'{0}\'\012")'
/opt/compiler-explorer/libs/fmt/11.0.0/include/fmt/base.h:2868:40:   in 'constexpr' expansion of 'fmt::v10::detail::parse_format_string<true, char, format_string_checker<char, regtype_t> >(((fmt::v10::basic_format_string<char, regtype_t&>*)this)->fmt::v10::basic_format_string<char, regtype_t&>::str_, fmt::v10::detail::format_string_checker<char, regtype_t>(fmt::v10::basic_string_view<char>(((const char*)s))))'
/opt/compiler-explorer/libs/fmt/11.0.0/include/fmt/base.h:2597:44:   in 'constexpr' expansion of 'fmt::v10::detail::parse_replacement_field<char, format_string_checker<char, regtype_t>&>((p + -1), end, (* & handler))'
/opt/compiler-explorer/libs/fmt/11.0.0/include/fmt/base.h:2573:35:   in 'constexpr' expansion of '(& handler)->fmt::v10::detail::format_string_checker<char, regtype_t>::on_replacement_field(adapter.fmt::v10::detail::parse_replacement_field<char, format_string_checker<char, regtype_t>&>(const char*, const char*, format_string_checker<char, regtype_t>&)::id_adapter::arg_id, begin)'
/opt/compiler-explorer/libs/fmt/11.0.0/include/fmt/base.h:2740:20:   in 'constexpr' expansion of '((fmt::v10::detail::format_string_checker<char, regtype_t>*)this)->fmt::v10::detail::format_string_checker<char, regtype_t>::on_format_specs(id, begin, begin)'
/opt/compiler-explorer/libs/fmt/11.0.0/include/fmt/base.h:2657:45: error: 'fmt::v10::detail::type_is_unformattable_for<regtype_t, char> _' has incomplete type
 2657 |     type_is_unformattable_for<T, char_type> _;
      |                                             ^
/opt/compiler-explorer/libs/fmt/11.0.0/include/fmt/base.h: In instantiation of 'constexpr fmt::v10::detail::value<Context> fmt::v10::detail::make_arg(T&) [with bool PACKED = true; Context = fmt::v10::context; T = regtype_t; typename std::enable_if<PACKED, int>::type <anonymous> = 0]':
/opt/compiler-explorer/libs/fmt/11.0.0/include/fmt/base.h:2002:74:   required from 'void fmt::v10::print(format_string<T ...>, T&& ...) [with T = {regtype_t&}; format_string<T ...> = basic_format_string<char, regtype_t&>]'
 2002 |   return {{detail::make_arg<NUM_ARGS <= detail::max_packed_args, Context>(
      |            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
 2003 |       args)...}};
      |       ~~~~~                                                               
<source>:31:15:   required from here
   31 |     fmt::print("Testing: '{0}'\n", regtypes["HOLDING"]);
      |     ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/libs/fmt/11.0.0/include/fmt/base.h:3016:44:   in 'constexpr' expansion of 'fmt::v10::make_format_args<>((* & args#0))'
/opt/compiler-explorer/libs/fmt/11.0.0/include/fmt/base.h:1625:63: error: 'fmt::v10::detail::type_is_unformattable_for<regtype_t, char> _' has incomplete type
 1625 |     type_is_unformattable_for<T, typename Context::char_type> _;
      |                                                               ^
/opt/compiler-explorer/libs/fmt/11.0.0/include/fmt/base.h:1628: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
 1628 |       formattable,
      |       ^~~~~~~~~~~
/opt/compiler-explorer/libs/fmt/11.0.0/include/fmt/base.h:1628:7: note: 'formattable' evaluates to false
Compiler returned: 1

Solution

  • auto format_as(regtype_t rt) definition is not available at the point of usage. The returned type can't be deduced from the declaration. Replace the declaration with the definition, or declare and define the function like std::string format_as(regtype_t rt), or auto format_as(regtype_t rt) -> std::string.