clang, fmtlib compilation issue

168 views Asked by At

I have just upgraded our code base to using fmt 10, and have got into a strange problem.

Fmt introduces format_as as a simple way of adding custom formatters for your own enums/classes, and also introduces formatting of std::optional.

However, the combination of those two fails to compile using clang, but compiles fine using gcc or msvc. An example can be found here: godbolt. Change compiler to gcc and the code compiles fine.

#include <fmt/format.h>
#include <fmt/std.h>
#include <optional>

namespace nn
{

enum class Bar
{
    First,
    Second,
    Third,
    Fifth = 5,
};

std::string_view format_as(Bar bar)
{
    switch (bar) {
    case Bar::First:
        return "One";
    case Bar::Second:
        return "Two";
    case Bar::Third:
        return "Three";
    case Bar::Fifth:
        return "Five";
    }
    return "";
}
}

int main() {
    auto b = nn::Bar::First;
    std::optional<nn::Bar> ob = b;
    

    fmt::print("{} {}\n", b, ob);

    return 0;
}

The problem seems to be that "fmt/std.h" calls u.set_debug_format(set); on line 170, and set_debug_format is private due to the private inheritance in fmt/format.h line 4055. If I add using base::set_debug_format; on line 4058, the code compiles also on clang.

Have anybode else stumled into this problem? Is it a bug in fmt, or a problem with the clang compiler, or me just doing something wrong?

1

There are 1 answers

0
ecatmur On

Yes, this is a bug in clang, reported at https://github.com/llvm/llvm-project/issues/68849. As discussed in https://github.com/scylladb/seastar/pull/1855, the workaround is to publicly inherit from the base formatter; in your case:

template<>
struct fmt::formatter<nn::Bar> : fmt::formatter<std::string_view> {
    using base = fmt::formatter<std::string_view>;
    auto format(nn::Bar b, auto& ctx) const { return base::format(format_as(b), ctx); }
};

Demo.

Note that this will change the output from optional(One) to optional("One") since the change to public inheritance will allow parse to call base::set_debug_format(); however, this is arguably preferable behavior for fmt.