How to mate std::chrono::minutes to boost::json tag_invoke value_from?

473 Views Asked by At

I try to store/load std::chrono::minutes to/from json using boost::jsons tag_invoke mechanism. While this works well for my custom little struct, I fail to formulate correct syntax for the chrono type.

I tried two straightforward versions:

void tag_invoke(boost::json::value_from_tag const&, boost::json::value &value, std::chrono::minutes const &minu);
std::chrono::minutes tag_invoke(boost::json::value_to_tag<std::chrono::minutes> const&, boost::json::value const &value);

and

namespace std::chrono {
void tag_invoke(boost::json::value_from_tag const&, boost::json::value &value, /*std::chrono::*/minutes const &minu);
/*std::chrono::*/minutes tag_invoke(boost::json::value_to_tag</*std::chrono::*/minutes> const&, boost::json::value const &value);
}

Both versions lead to the same compiler errors (g++ 11.3), which include

/.../_deps/boost-src/libs/json/include/boost/json/value_from.hpp:87: error: no matching function for call to ‘value_from_impl(std::chrono::duration<long int, std::ratio<60> >, std::remove_reference<boost::json::storage_ptr&>::type)’

which seems to be the typical marker that my overload is not found/considered.

What is the quirk that I miss here?

1

There are 1 best solutions below

1
On BEST ANSWER

You correctly surmised you need to put the declaration in a namespace associated via ADL.

Both the boost::json and std::chrono work for me:

Live On Coliru

#include <boost/json/src.hpp> //for header-only
#include <chrono>
#include <iostream>

namespace json = boost::json;
using namespace std::chrono_literals;
using M = std::chrono::minutes;

namespace NS {
    void tag_invoke(json::value_from_tag, json::value& v, M const& d) {
        v = {{"minutes", d.count()}};
    }
    M tag_invoke(json::value_to_tag<M>, json::value const& v) {
        return M(v.at("minutes").as_int64());
    }
} // namespace NS

int main() {
    auto jv = json::value_from(5min);
    std::cout << jv << " " << std::boolalpha
              << (value_to<M>(jv) == 300s) << "\n";
}

Output:

g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp -o bj -DNS=boost::json
g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp -o sc -DNS=std::chrono
./bj
{"minutes":5} true
./sc
{"minutes":5} true

Perhaps with your standard library implementation std::chrono::minutes is a typedef for something other (not declared in that exact namespace)? I'm not sure whether the standard allows for that actually

Generalize!

Regardless of what you end up doing, I recommend not special casing a particular ratio. Instead generically support all durations!

Live On Coliru

#include <boost/json/src.hpp> // for header-only
#include <chrono>
#include <iostream>

namespace json = boost::json;
using namespace std::chrono_literals;

namespace boost::json {
    template <typename Rep, typename Ratio>
    void tag_invoke(value_from_tag, value& v, std::chrono::duration<Rep, Ratio> const& d) {
        v = {{"seconds", static_cast<double>(d / 1.0s)}};
    }

    template <typename Duration, typename = typename Duration::rep>
    Duration tag_invoke(value_to_tag<Duration>, value const& v) {
        return std::chrono::duration_cast<Duration>(v.at("seconds").as_double() * 1s);
    }
} // namespace boost::json

template <typename D> void foo(D d) {
    auto jv        = json::value_from(d);
    auto roundtrip = value_to<D>(jv);
    auto delta     = (roundtrip - d) / 1.0ns;
    auto ok        = std::abs(delta) < 0.000'1; // < 100 femtosecond

    std::cout << jv << "\t" << ok << '\t' << delta << "ns\n";
}

int main() {
    std::cout << std::boolalpha;
    foo(5min);
    foo(5.25min);
    foo(30h - 15min + 5s);
    foo(20ms);
}

Prints e.g.

{"seconds":3E2} true    0ns
{"seconds":3.15E2}  true    0ns
{"seconds":1.07105E5}   true    0ns
{"seconds":2E-2}    true    0ns

Or with -ffast-math:

{"seconds":3E2} true    0ns
{"seconds":3.15E2}  true    2.60209e-08ns
{"seconds":1.07105E5}   true    0ns
{"seconds":2E-2}    true    0ns

No Floating Point?

To reduce the inexact FP representation issue, pick a resolution: http://coliru.stacked-crooked.com/a/c5b8e084b5d47f3f

More strictly, you can outlaw floating point representation in the overload: http://coliru.stacked-crooked.com/a/caca13072524b0f9