From 710797a4a724ff74b0807031c612b518edf977e8 Mon Sep 17 00:00:00 2001 From: Emil Dotchevski Date: Sun, 1 Feb 2026 13:56:15 -0500 Subject: [PATCH] Better SFINAE in JSON encoders --- doc/leaf.adoc | 23 +++++++++++-------- include/boost/leaf/detail/encoder.hpp | 7 +++++- .../leaf/serialization/boost_json_encoder.hpp | 20 ++++++++-------- .../serialization/nlohmann_json_encoder.hpp | 7 ++++-- test/boost_json_encoder_test.cpp | 16 +++++++++++++ 5 files changed, 49 insertions(+), 24 deletions(-) diff --git a/doc/leaf.adoc b/doc/leaf.adoc index c4726b9..55272a3 100644 --- a/doc/leaf.adoc +++ b/doc/leaf.adoc @@ -2873,8 +2873,9 @@ namespace serialization { boost::json::value & v_; - // Enabled if x is assignable to boost::json::value, or - // if tag_invoke is defined for boost::json::value_from_tag. + // Uses unspecified SFINAE expression designed to select this overload + // only if no other compatible overload is found. + // Implemented in terms of boost::json::value_from. template friend void output( boost_json_encoder &, T const & x ); @@ -2906,7 +2907,9 @@ namespace serialization { Json & j_; - // Enabled if to_json is available for Json and T. + // Uses unspecified SFINAE expression designed to select this overload + // only if no other compatible overload is found. + // Implemented in terms of to_json. template friend void output( nlohmann_json_encoder &, T const & x ); @@ -3667,8 +3670,9 @@ namespace serialization { boost::json::value & v_; - // Enabled if x is assignable to boost::json::value, or - // if tag_invoke is defined for boost::json::value_from_tag. + // Uses unspecified SFINAE expression designed to select this overload + // only if no other compatible overload is found. + // Implemented in terms of boost::json::value_from. template friend void output( boost_json_encoder &, T const & x ); @@ -3680,10 +3684,7 @@ namespace serialization } } ---- -The `boost_json_encoder` type serializes error objects to JSON format using https://www.boost.org/doc/libs/release/libs/json/[Boost.JSON]. The `output` function is enabled for: - -* Types directly assignable to `boost::json::value` -* Types for which a `tag_invoke` overload for `value_from_tag` can be found via ADL +The `boost_json_encoder` type serializes error objects to JSON format using https://www.boost.org/doc/libs/release/libs/json/[Boost.JSON]. The `output` function is implemented in terms of `boost::json::value_from`. See <>. @@ -4489,7 +4490,9 @@ namespace serialization { Json & j_; - // Enabled if to_json is available for Json and T. + // Uses unspecified SFINAE expression designed to select this overload + // only if no other compatible overload is found. + // Implemented in terms of to_json. template friend void output( nlohmann_json_encoder &, T const & x ); diff --git a/include/boost/leaf/detail/encoder.hpp b/include/boost/leaf/detail/encoder.hpp index 9aa0bf4..0fa4321 100644 --- a/include/boost/leaf/detail/encoder.hpp +++ b/include/boost/leaf/detail/encoder.hpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include namespace boost { namespace leaf { @@ -16,7 +18,10 @@ namespace serialization struct encoder_adl {}; template - auto output(Encoder & e, T const & x) -> decltype(output(e, x.value)) + typename std::enable_if< + sizeof(T) == sizeof(decltype(std::declval().value)), + decltype(output(std::declval(), std::declval().value), void())>::type + output(Encoder & e, T const & x) { output(e, x.value); } diff --git a/include/boost/leaf/serialization/boost_json_encoder.hpp b/include/boost/leaf/serialization/boost_json_encoder.hpp index 4eac1b8..bf32649 100644 --- a/include/boost/leaf/serialization/boost_json_encoder.hpp +++ b/include/boost/leaf/serialization/boost_json_encoder.hpp @@ -5,12 +5,15 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#include #include namespace boost { namespace json { class value; -struct value_from_tag; + +template +void value_from(T &&, value &); } } @@ -18,21 +21,16 @@ namespace boost { namespace leaf { namespace serialization { - template + template struct boost_json_encoder_ { Value & v_; - template - friend auto output(boost_json_encoder_ & e, T const & x) -> decltype(std::declval() = x, void()) + template + friend typename std::enable_if::value>::type + output(Encoder & e, T const & x, Deprioritize...) { - e.v_ = x; - } - - template - friend auto output(boost_json_encoder_ & e, T const & x) -> decltype(tag_invoke(std::declval(), std::declval(), x), void()) - { - tag_invoke(ValueFromTag{}, e.v_, x); + boost::json::value_from(x, e.v_); } template diff --git a/include/boost/leaf/serialization/nlohmann_json_encoder.hpp b/include/boost/leaf/serialization/nlohmann_json_encoder.hpp index 60b616f..3e1cb41 100644 --- a/include/boost/leaf/serialization/nlohmann_json_encoder.hpp +++ b/include/boost/leaf/serialization/nlohmann_json_encoder.hpp @@ -5,6 +5,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#include #include namespace boost { namespace leaf { @@ -16,10 +17,12 @@ namespace serialization { Json & j_; - template - friend auto output(nlohmann_json_encoder & e, T const & x) -> decltype(to_json(std::declval(), x), void()) + template + friend typename std::enable_if::value, Json *>::type + output(Encoder & e, T const & x, Deprioritize...) { to_json(e.j_, x); + return 0; } template diff --git a/test/boost_json_encoder_test.cpp b/test/boost_json_encoder_test.cpp index b570910..7fb4e13 100644 --- a/test/boost_json_encoder_test.cpp +++ b/test/boost_json_encoder_test.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #if BOOST_LEAF_CFG_STD_SYSTEM_ERROR # include #endif @@ -76,6 +77,11 @@ struct my_error } }; +struct my_error_with_vector +{ + std::vector value; +}; + leaf::result fail() { return BOOST_LEAF_NEW_ERROR( @@ -87,6 +93,7 @@ leaf::result fail() 42, my_error<1>{1, "error one"}, my_error<2>{2, "error two"}, + my_error_with_vector{{10, 20, 30}}, leaf::e_errno{ENOENT}, leaf::e_api_function{"my_api_function"} ); } @@ -103,6 +110,7 @@ void leaf_throw() 42, my_error<1>{1, "error one"}, my_error<2>{2, "error two"}, + my_error_with_vector{{10, 20, 30}}, leaf::e_errno{ENOENT}, leaf::e_api_function{"my_api_function"} ); } @@ -118,6 +126,7 @@ void throw_() 42, my_error<1>{1, "error one"}, my_error<2>{2, "error two"}, + my_error_with_vector{{10, 20, 30}}, leaf::e_errno{ENOENT}, leaf::e_api_function{"my_api_function"} ); throw my_exception{}; @@ -167,6 +176,13 @@ void check_diagnostic_details(boost::json::value const & j, bool has_source_loca BOOST_TEST_EQ(boost::json::value_to(e2j.at("code")), 2); BOOST_TEST_EQ(boost::json::value_to(e2j.at("message")), "error two"); + auto const & vj = j.at("my_error_with_vector"); + BOOST_TEST(vj.is_array()); + BOOST_TEST_EQ(vj.as_array().size(), 3); + BOOST_TEST_EQ(boost::json::value_to(vj.as_array()[0]), 10); + BOOST_TEST_EQ(boost::json::value_to(vj.as_array()[1]), 20); + BOOST_TEST_EQ(boost::json::value_to(vj.as_array()[2]), 30); + auto const & ej = j.at("boost::leaf::e_errno"); BOOST_TEST_EQ(boost::json::value_to(ej.at("errno")), ENOENT); BOOST_TEST(!boost::json::value_to(ej.at("strerror")).empty());