rollbear / trompeloeil

Header only C++14 mocking framework
Boost Software License 1.0
802 stars 85 forks source link

How to make it print using fmt? #251

Closed reddwarf69 closed 2 years ago

reddwarf69 commented 3 years ago

I am not really proficient with templates. But with Catch I have done

namespace Catch {

template<typename T>
struct StringMaker<T, typename std::enable_if<fmt::is_formattable<T>::value>::type>
{
    static std::string convert(const T& value) { return fmt::format("{}", value); }
};

}

to make it print types that don't have a stream insertion operator but can be formatted with fmt, and it seems to work fine.

I have seen https://github.com/rollbear/trompeloeil/blob/main/docs/CookBook.md/#custom_formatting, but it's not clear to me where I should put the enable_if.

There is a need for a typename = void (https://github.com/catchorg/Catch2/blob/v2.13.7/single_include/catch2/catch.hpp#L1612) in https://github.com/rollbear/trompeloeil/blob/v41/include/trompeloeil.hpp#L1424?

rollbear commented 3 years ago

Damned! This is an oversight of mine, and a painful one since it's made so recently. It's not hard to fix, but it's hard to fix without making it a breaking change. I have to think a bit about this. Thanks for reporting.

rollbear commented 3 years ago

Probably fixed on branch sfinaed_custom_print. Please check it out and let me know. An example for how to write such a SFINAE'd printer is in the self test program:

https://github.com/rollbear/trompeloeil/blob/sfinaed_custom_print/test/compiling_tests_11.cpp#L4296-L4304

I haven't updated the docs yet.

reddwarf69 commented 3 years ago

It does work for this simple example

#include <trompeloeil.hpp>
#include <fmt/format.h>

struct formattable {
    int x;
};

struct non_formattable {
    int x;
};

template<>
struct fmt::formatter<formattable>
{
    auto parse(fmt::format_parse_context& ctx) -> decltype(ctx.begin())
    {
        if (ctx.begin() != ctx.end()) {
            throw fmt::format_error("invalid format");
        }

        return ctx.begin();
    }

    template<typename FormatContext>
    auto format(const formattable& val, FormatContext& ctx) -> decltype(ctx.out())
    {
        return format_to(ctx.out(), "{}", val.x);
    }
};

namespace trompeloeil {

template<typename T>
struct printer<T, typename std::enable_if<fmt::is_formattable<T>::value>::type>
{
    static void print(std::ostream& os, const T& t) { os << fmt::format("{}", t); }
};

}

class Interface
{
public:
  virtual ~Interface() = default;
  virtual void foo(formattable) = 0;
  virtual void bar(non_formattable) = 0;
};

void interface_func(Interface*); // function to test

class Mock : public Interface
{
public:
  MAKE_MOCK1(foo, void(formattable),override);
  MAKE_MOCK1(bar, void(non_formattable),override);
};

int main()
{
  using trompeloeil::_;  // wild card for matching any value
  using trompeloeil::gt; // greater-than match

  Mock m;

  //REQUIRE_CALL(m, foo(_));

  m.bar(non_formattable{10});
  m.foo(formattable{5});
}

I get a compilation error with my original code (which I can't share, the following is manually edited):

/include/fmt/core.h: In instantiation of ‘fmt::v8::basic_string_view<Char>::basic_string_view(S) [with S = fmt::v8::detail::std_string_view<char>; typename std::enable_if<std::is_same<S, fmt::v8::detail::std_string_view<Char> >::value, int>::type <anonymous> = 0; Char = char]’:
/include/fmt/core.h:1273:42:   required from ‘fmt::v8::basic_string_view<typename S::char_type> fmt::v8::detail::arg_mapper<Context>::map(const T&) [with T = trompeloeil::wildcard; typename std::enable_if<((((std::is_constructible<fmt::v8::detail::std_string_view<typename Context::char_type>, T>::value && (! std::is_constructible<fmt::v8::basic_string_view<typename S::char_type>, T>::value)) && (! fmt::v8::detail::is_string<T>::value)) && (! std::is_constructible<typename Context::formatter_type<T> >::value)) && (! std::is_constructible<fmt::v8::detail::fallback_formatter<T, typename Context::char_type> >::value)), int>::type <anonymous> = 0; Context = fmt::v8::basic_format_context<fmt::v8::appender, char>; typename S::char_type = char]’
/include/fmt/core.h:1566:46:   required from ‘fmt::v8::detail::value<Context> fmt::v8::detail::make_arg(const T&) [with bool IS_PACKED = true; Context = fmt::v8::basic_format_context<fmt::v8::appender, char>; fmt::v8::detail::type <anonymous> = fmt::v8::detail::type::string_type; T = trompeloeil::wildcard; typename std::enable_if<IS_PACKED, int>::type <anonymous> = 0]’
/include/fmt/core.h:1694:64:   required from ‘fmt::v8::format_arg_store<Context, Args>::format_arg_store(const Args& ...) [with Context = fmt::v8::basic_format_context<fmt::v8::appender, char>; Args = {trompeloeil::wildcard}]’
/include/fmt/core.h:1710:18:   required from ‘constexpr fmt::v8::format_arg_store<Context, Args ...> fmt::v8::make_format_args(const Args& ...) [with Context = fmt::v8::basic_format_context<fmt::v8::appender, char>; Args = {trompeloeil::wildcard}]’
/include/fmt/core.h:2886:44:   required from ‘std::string fmt::v8::format(fmt::v8::format_string<T ...>, T&& ...) [with T = {const trompeloeil::wildcard&}; std::string = std::__cxx11::basic_string<char>; fmt::v8::format_string<T ...> = fmt::v8::basic_format_string<char, const trompeloeil::wildcard&>]’
src/task_runner/lib/task_runner_template/test/task_runner.cpp:25:72:   [ skipping 2 instantiation contexts, use -ftemplate-backtrace-limit=0 to disable ]
/include/trompeloeil.hpp:1489:10:   required from ‘void trompeloeil::print_expectation(std::ostream&, const T&) [with T = trompeloeil::wildcard; std::ostream = std::basic_ostream<char>]’
/include/trompeloeil.hpp:3191:39:   required from ‘void trompeloeil::print_mismatch(std::ostream&, size_t, const V&, const P&) [with V = trompeloeil::wildcard; P = std::function<void(task_runner::http_result&&)>; std::ostream = std::basic_ostream<char>; size_t = long unsigned int]’
/include/trompeloeil.hpp:3203:69:   required from ‘void trompeloeil::print_mismatch(std::ostream&, trompeloeil::detail::index_sequence<I ...>, const std::tuple<_Tps ...>&, const std::tuple<_Types ...>&) [with V = {task_runner::http_task, trompeloeil::wildcard}; P = {const task_runner::http_task&, std::function<void(task_runner::http_result&&)>&}; long unsigned int ...I = {0, 1}; std::ostream = std::basic_ostream<char>; trompeloeil::detail::index_sequence<I ...> = trompeloeil::detail::integer_sequence<long unsigned int, 0, 1>]’
/include/trompeloeil.hpp:3212:19:   required from ‘void trompeloeil::print_mismatch(std::ostream&, const std::tuple<_Tps ...>&, const std::tuple<_Types ...>&) [with V = {task_runner::http_task, trompeloeil::wildcard}; P = {const task_runner::http_task&, std::function<void(task_runner::http_result&&)>&}; std::ostream = std::basic_ostream<char>]’
/include/trompeloeil.hpp:4060:38:   required from ‘std::ostream& trompeloeil::call_matcher<Sig, Value>::report_mismatch(std::ostream&, trompeloeil::call_params_type_t<Sig>&) [with Sig = void(const task_runner::http_task&, std::function<void(task_runner::http_result&&)>&&); Value = std::tuple<task_runner::http_task, trompeloeil::wildcard>; std::ostream = std::basic_ostream<char>; trompeloeil::call_params_type_t<Sig> = std::tuple<const task_runner::http_task&, std::function<void(task_runner::http_result&&)>&>]’
/include/trompeloeil.hpp:4040:5:   required from here
/include/fmt/core.h:477:63: error: ‘struct fmt::v8::detail::std_string_view<char>’ has no member named ‘data’
  477 |   FMT_CONSTEXPR basic_string_view(S s) FMT_NOEXCEPT : data_(s.data()),
      |                                                             ~~^~~~
/include/fmt/core.h:478:63: error: ‘struct fmt::v8::detail::std_string_view<char>’ has no member named ‘size’
  478 |                                                       size_(s.size()) {}
      |                                                             ~~^~~~

I guess it may be a problem with my code, not sure.

rollbear commented 3 years ago

I'll try to see if I can figure something out. fmt version 8.0.1, I guess? Are you using FMT_ENFORCE_COMPILE_STRING? Which compiler? If clang, which stdlib? Building as C++11/14/17/20?

reddwarf69 commented 3 years ago
#include <trompeloeil.hpp>
#include <fmt/format.h>

#ifdef WITH_PRINTER
namespace trompeloeil {

template<typename T>
struct printer<T, typename std::enable_if<fmt::is_formattable<T>::value>::type>
{
    static void print(std::ostream& os, const T& t) { os << fmt::format("{}", t); }
};

} // namespace trompeloeil
#endif

class Mock
{
  public:
    TROMPELOEIL_MAKE_MOCK1(baz, void(int));
};

int main()
{
    Mock mock;
#ifdef WITH_WILDCARD
    REQUIRE_CALL_V(mock, baz(trompeloeil::_));
#else
    REQUIRE_CALL_V(mock, baz(4));
#endif
}
$ for I in WITHOUT_PRINTER WITH_PRINTER; do for J in WITHOUT_WILDCARD WITH_WILDCARD; do for STD in 11 14; do echo $I $J $STD; g++ -std=gnu++$STD -o sfinae.o -c sfinae.cpp -D$I -D$J 2>&1 | tail -n 2; done; done; done
WITHOUT_PRINTER WITHOUT_WILDCARD 11
WITHOUT_PRINTER WITHOUT_WILDCARD 14
WITHOUT_PRINTER WITH_WILDCARD 11
WITHOUT_PRINTER WITH_WILDCARD 14
WITH_PRINTER WITHOUT_WILDCARD 11
WITH_PRINTER WITHOUT_WILDCARD 14
WITH_PRINTER WITH_WILDCARD 11
  478 |                                                       size_(s.size()) {}
      |                                                             ~~^~~~
WITH_PRINTER WITH_WILDCARD 14

Compiles fine with clang 12, the problem only happend with gcc 11.2.1 (Fedora 34). fmt 8.0.1

(I was actually sure the problem was happening also with clang, but my cmake build was actually misconfigured and using also gcc when I though it was using clang).

rollbear commented 3 years ago

Good detective work there. Thanks! I can now reproduce it myself.

rollbear commented 3 years ago

This is all very odd. For some reason, the wildcard passes the enable_if for being formattable. It shouldn't, of course, butwildcard is a very evil type that claims to be convertible to any type, so I guess it has GCC confused. Since this only happens with GCC and only with C++11, I'm inclined to think that it's probably a compiler bug. Regardless, the fix is simple, a printer<wildcard> specialization is included, which is a perfect match, so no partial specializations will be attempted.

Please try the branch again and see if I messed something else up in the process.

reddwarf69 commented 3 years ago

Yes, that fixes my issue. From my PoV it's good to go.

rollbear commented 3 years ago

Excellent! Thanks for reporting and testing out my attempts. Merged to main now. Keeping the issue open until a release has been tagged.

reddwarf69 commented 3 years ago

https://github.com/rollbear/trompeloeil/commit/4990ce3a97a2b6f606905c65009ad39ce315c103#diff-347324177520611d7bdf6a833033f66debdaa20c3a6cfb67ca2d2052efea29bdR1901

every types that -> every type that

can be printed using a custom SFINAE:d printer like: -> can be printed using a custom SFINAE printer like:

rollbear commented 2 years ago

Fix included in release v42