strong_type icon indicating copy to clipboard operation
strong_type copied to clipboard

format regression

Open timblechmann opened this issue 2 years ago • 13 comments

181c9f4ff120b2e0e3b7b1e793e5833b9b931c3c causes this compile error with both gcc and clang:

/usr/include/c++/12/type_traits: In substitution of ‘template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = false; _Tp = fmt::v8::formatter<long unsigned int, char, void>]’:
/path/to/strong_type/include/strong_type/strong_type.hpp:1707:8:   required from ‘struct fmt::v8::formatter<strong::type<long unsigned int, my_tag, strong::equality, strong::ostreamable, strong::ordered, strong::default_constructible>, char, void>’
/usr/include/c++/12/type_traits:971:30:   required from ‘struct std::__is_constructible_impl<fmt::v8::formatter<strong::type<long unsigned int, my_tag, strong::equality, strong::ostreamable, strong::ordered, strong::default_constructible>, char, void> >’
/usr/include/c++/12/type_traits:977:12:   required from ‘struct std::is_constructible<fmt::v8::formatter<strong::type<long unsigned int, my_tag, strong::equality, strong::ostreamable, strong::ordered, strong::default_constructible>, char, void> >’
/path/to/fmt-8.1.0/include/fmt/core.h:1472:13:   required by substitution of ‘template<class T, class U, typename std::enable_if<((((! fmt::v8::detail::is_string<U>::value) && (! fmt::v8::is_char<U>::value)) && (! std::is_array<_To>::value)) && (std::is_constructible<fmt::v8::formatter<U, char, void> >::value || std::is_constructible<fmt::v8::detail::fallback_formatter<U, char, void> >::value)), int>::type <anonymous> > constexpr decltype (((fmt::v8::detail::arg_mapper<fmt::v8::basic_format_context<fmt::v8::appender, char> >*)this)->fmt::v8::detail::arg_mapper<fmt::v8::basic_format_context<fmt::v8::appender, char> >::do_map(forward<T>(val))) fmt::v8::detail::arg_mapper<fmt::v8::basic_format_context<fmt::v8::appender, char> >::map(T&&) [with T = const strong::type<long unsigned int, my_tag, strong::equality, strong::ostreamable, strong::ordered, strong::default_constructible>&; U = strong::type<long unsigned int, my_tag, strong::equality, strong::ostreamable, strong::ordered, strong::default_constructible>; typename std::enable_if<((((! fmt::v8::detail::is_string<U>::value) && (! fmt::v8::is_char<U>::value)) && (! std::is_array<_To>::value)) && (std::is_constructible<fmt::v8::formatter<U, char, void> >::value || std::is_constructible<fmt::v8::detail::fallback_formatter<U, char, void> >::value)), int>::type <anonymous> = <missing>]’
/path/to/fmt-8.1.0-R1/include/fmt/core.h:2670:41:   required from ‘constexpr decltype (ctx.begin()) fmt::v8::detail::parse_format_specs(ParseContext&) [with T = strong::type<long unsigned int, my_tag, strong::equality, strong::ostreamable, strong::ordered, strong::default_constructible>; ParseContext = compile_parse_context<char, error_handler>; decltype (ctx.begin()) = const char*]’
/path/to/fmt-8.1.0-R1/include/fmt/core.h:2906:22:   required from ‘constexpr fmt::v8::detail::format_string_checker<Char, ErrorHandler, Args>::format_string_checker(fmt::v8::basic_string_view<Char>, ErrorHandler) [with Char = char; ErrorHandler = fmt::v8::detail::error_handler; Args = {strong::type<long unsigned int, my_tag, strong::equality, strong::ostreamable, strong::ordered, strong::default_constructible>, long unsigned int}]’
/path/to/source.h:80:21:   required from here
/usr/include/c++/12/type_traits:2614:11: error: no type named ‘type’ in ‘struct std::enable_if<false, fmt::v8::formatter<long unsigned int, char, void> >’
 2614 |     using enable_if_t = typename enable_if<_Cond, _Tp>::type;

timblechmann avatar Sep 07 '22 00:09 timblechmann

Can you provide an example program? I fail to reproduce this.

rollbear avatar Sep 21 '22 08:09 rollbear

it seems to be related to fmt's ostream support and strong::ostreamable:

#include <strong_type/strong_type.hpp>

#include <fmt/format.h>
#include <fmt/ostream.h>

using type = strong::
    type< uint64_t, struct tag, strong::ostreamable, strong::default_constructible >;

void foo()
{
    fmt::print("{}", type{});
}

timblechmann avatar Sep 21 '22 13:09 timblechmann

Right. Confirmed. Thanks. It does work if you use strong::formattable, but it should work with ostreamable too.

rollbear avatar Sep 21 '22 14:09 rollbear

After some digging, I now understand what causes the problem, but I'm not yet sure how to solve it. The type ::fmt::formatter<type...> must conditionally exist only if strong::formattable is used, which was the case with the earlier SFINAE solution.

rollbear avatar Sep 21 '22 14:09 rollbear

Can you give branch fix_fmt a try? I believe it works.

rollbear avatar Sep 23 '22 09:09 rollbear

~~the STONG_CONSTEXPR caused problems for me:~~

namespace fmt
{
template<typename T, typename Tag, typename... M, typename Char>
struct formatter<::strong::type<T, Tag, M...>, Char> : formatter<T, Char>
{
  template<typename FormatContext, typename Type>
//  STRONG_CONSTEXPR
  decltype(auto)
  format(const Type& t, FormatContext& fc) const
      noexcept(noexcept(std::declval<formatter<T, Char>>().format(std::declval<const T&>(), fc)))
  {
    return formatter<T, Char>::format(value_of(t), fc);
  }
};
}

~~i believe this is because the formatter::format function of my formatters is not constexpr~~

my formatter::format functions were not const, causing build failures, making them const, it works fine

timblechmann avatar Sep 23 '22 10:09 timblechmann

They originally were non-const, but I noticed that they were const in the {fmt} API examples. I can revert them to non-const.

rollbear avatar Sep 23 '22 11:09 rollbear

Unfortunately it's too forgiving now and allows formatting even without either formattable or ostreamable, so I still have some work to do. :disappointed:

rollbear avatar Sep 23 '22 11:09 rollbear

They originally were non-const, but I noticed that they were const in the {fmt} API examples. I can revert them to non-const.

no strong opinion from my side. in my cases the formatters are definitely const, though it may not be impossible to have non-const formatters for esoteric use cases

timblechmann avatar Sep 23 '22 11:09 timblechmann

OK, now it works. I reverted back to non-const format functions. {fmt} is OK with it, and the implementation can call a const version for the underlying type. The reverse is not true, as you noticed.

There's one somewhat esoteric caveat, though. Since version 9,{fmt} requires you to define a formatter<T> for your type even if you have << for stream insertion. In that case you let your formatter inherit from fmt::ostream_formatter and have an empty body. The library creates it for you, but this means that if you use both strong::formattable and strong::ostreamable for your type, you'll get two conflicting definitions of fmt::formatter<> for your type. I guess I could make one of them dominate in that case.

Do you have an opinion on this?

rollbear avatar Sep 23 '22 16:09 rollbear

Fixed it so that if a type is both formattable and ostreamable, any use of fmt will use the formatter<> for the underlying type.

This was very tricky to get to work.

rollbear avatar Sep 23 '22 19:09 rollbear

I'm claiming this one done now. Wow, what a ride. Leaving it open until a release has been tagged. If you disagree that it is indeed fixed, please let me know here and I'll remove the "fixed" label.

rollbear avatar Sep 23 '22 19:09 rollbear

many thanks! i've tested it with my codebase and it works nicely!

timblechmann avatar Sep 24 '22 01:09 timblechmann

Included in release v5

rollbear avatar Oct 20 '22 08:10 rollbear