Closed reddwarf69 closed 2 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.
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:
I haven't updated the docs yet.
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.
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?
#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).
Good detective work there. Thanks! I can now reproduce it myself.
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.
Yes, that fixes my issue. From my PoV it's good to go.
Excellent! Thanks for reporting and testing out my attempts. Merged to main now. Keeping the issue open until a release has been tagged.
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:
Fix included in release v42
I am not really proficient with templates. But with Catch I have done
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?