2

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?

kaba
  • 362
  • 1
  • 13

1 Answers1

2

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

sehe
  • 374,641
  • 47
  • 450
  • 633
  • 1
    Thanks a lot for your elaborate reply! Seeing that your (first) version and mine do not differ substantially, nailed it down to your's beeing all in one file. ... which led me to a missing include :( . The `tag_invoke` overload has to be known at compile time; link time is too late! Additionally your further thoughts and remarks are very interesting. Although I'll stick with integral minutes for this project (I won't need anything else), I'll remember your suggestions for other projects! – kaba Feb 20 '23 at 22:29