Skip to content

Perplexing template deduction failure serialising a 3rd party type using base class #3267

Closed
@willat343

Description

@willat343

I know (and have checked with a basic code example) that if a serialisation is defined for a base class/struct, then derived classes can be serialised too without additional code (the derived classes are equivalent to the base in the json). For example the following code works as intended:

#include <iostream>
struct A { int i; };
struct B : public A { void do_something(){} };

void to_json(nlohmann::json& j, const A& a) {
    j["a"] = a.i;
}

void from_json(const nlohmann::json& j, A& a) {
    j.at("a").get_to(a.i);
}

int main() {
     B b;
     b.i = 6;
     nlohmann::json j = b;
     std::cerr << j.dump() << "\n";
     B b2 = j.get<B>();
     if (b2.i == b.i) {
         std::cerr << "correctly deserialised!\n";
     }
     return 0;
}

I am facing an equivalent issue with a 3rd party type, in particular Eigen matrices/vectors, which seems to have nothing to do with the contents of the serialisation functions themselves, but the deduction that these to_json/from_json functions need to be used in the template deduction. The following code (which i do not want to use for reasons I will discuss in a moment) is tested and works as intended:

// header file
namespace Eigen {
    template<typename Scalar, int Rows, int Cols>
    void to_json(nlohmann::json& j, const Matrix<Scalar, Rows, Cols>& matrix) {
        for (int row = 0; row < matrix.rows(); ++row) {
            nlohmann::json column = nlohmann::json::array();
            for (int col = 0; col < matrix.cols(); ++col) {
                column.push_back(matrix(row, col));
            }
            j.push_back(column);
        }
    }

    template<typename Scalar, int Rows, int Cols>
    void from_json(const nlohmann::json& j, Matrix<Scalar, Rows, Cols>& matrix) {        
        for (std::size_t row = 0; row < j.size(); ++row) {
            const auto& jrow = j.at(row);
            for (std::size_t col = 0; col < jrow.size(); ++col) {
                const auto& value = jrow.at(col);
                value.get_to(matrix(row, col));
            }
        }
    }
}

// source file
int main() {
    Eigen::Matrix4f m;
    m <<  1.0,   2.0,   3.0,   4.0,
             11.0, 12.0, 13.0, 14.0,
             21.0, 22.0, 23.0, 24.0,
             31.0, 32.0, 33.0, 34.0;
    nlohmann::json j = m;
    std::cerr << j.dump() << std::endl;
    auto m2 = j.get<Eigen::Matrix4f>();
    std::cerr << m2 << "\n";
    return 0;
}

(note that Matrix4f is a typedef for Matrix<float, 4, 4>)

As described in their documentation (https://eigen.tuxfamily.org/dox/TopicFunctionTakingEigenTypes.html), it is more general and powerful to templatise according to an appropriate base class which will permit serialisation of a wider group of eigen structures. Noting that Matrix4f inherits from MatrixBase<Matrix4f>, the following serialisation functions should work identically:

namespace Eigen {
template<typename Derived>
    void to_json(nlohmann::json& j, const MatrixBase<Derived>& matrix) {
        for (int row = 0; row < matrix.rows(); ++row) {
            nlohmann::json column = nlohmann::json::array();
            for (int col = 0; col < matrix.cols(); ++col) {
                column.push_back(matrix(row, col));
            }
            j.push_back(column);
        }
    }

    template<typename Derived>
    void from_json(const nlohmann::json& j, MatrixBase<Derived>& matrix) {        
        for (std::size_t row = 0; row < j.size(); ++row) {
            const auto& jrow = j.at(row);
            for (std::size_t col = 0; col < jrow.size(); ++col) {
                const auto& value = jrow.at(col);
                value.get_to(matrix(row, col));
            }
        }
    }
}

This does not compile, with the first part of the error log (to me) showing that it is not deducing that it should use these serialisation functions, but instead trying to use an array type converter (void nlohmann::detail::to_json(BasicJsonType&, const CompatibleArrayType&) [with BasicJsonType = nlohmann::basic_json<>; CompatibleArrayType = Eigen::Matrix<float, 4, 4>...):

In file included from /home/williamtalbot/temp/eigenjsontest/eigenjsontest.cpp:1:0:
/usr/local/include/nlohmann/json.hpp: In instantiation of ‘static void nlohmann::detail::external_constructor<(nlohmann::detail::value_t)2>::construct(BasicJsonType&, const CompatibleArrayType&) [with BasicJsonType = nlohmann::basic_json<>; CompatibleArrayType = Eigen::Matrix<float, 4, 4>; typename std::enable_if<(! std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value), int>::type <anonymous> = 0]’:
/usr/local/include/nlohmann/json.hpp:4276:52:   required from ‘void nlohmann::detail::to_json(BasicJsonType&, const CompatibleArrayType&) [with BasicJsonType = nlohmann::basic_json<>; CompatibleArrayType = Eigen::Matrix<float, 4, 4>; typename std::enable_if<((((nlohmann::detail::is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value && (! nlohmann::detail::is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value)) && (! nlohmann::detail::is_compatible_string_type<BasicJsonType, ConstructibleStringType>::value)) && (! std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value)) && (! nlohmann::detail::is_basic_json<T>::value)), int>::type <anonymous> = 0]’
/usr/local/include/nlohmann/json.hpp:4353:23:   required from ‘decltype ((nlohmann::detail::to_json(j, forward<T>(val)), void())) nlohmann::detail::to_json_fn::operator()(BasicJsonType&, T&&) const [with BasicJsonType = nlohmann::basic_json<>; T = Eigen::Matrix<float, 4, 4>&; decltype ((nlohmann::detail::to_json(j, forward<T>(val)), void())) = void]’
/usr/local/include/nlohmann/json.hpp:4403:28:   required from ‘static decltype ((nlohmann::{anonymous}::to_json(j, forward<ValueType>(val)), void())) nlohmann::adl_serializer<T, SFINAE>::to_json(BasicJsonType&, ValueType&&) [with BasicJsonType = nlohmann::basic_json<>; ValueType = Eigen::Matrix<float, 4, 4>&; <template-parameter-1-1> = Eigen::Matrix<float, 4, 4>; <template-parameter-1-2> = void; decltype ((nlohmann::{anonymous}::to_json(j, forward<ValueType>(val)), void())) = void]’
/usr/local/include/nlohmann/json.hpp:17931:35:   required from ‘nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType>::basic_json(CompatibleType&&) [with CompatibleType = Eigen::Matrix<float, 4, 4>&; U = Eigen::Matrix<float, 4, 4>; typename std::enable_if<((! nlohmann::detail::is_basic_json<U>::value) && nlohmann::detail::is_compatible_type<nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType>, U>::value), int>::type <anonymous> = 0; ObjectType = std::map; ArrayType = std::vector; StringType = std::__cxx11::basic_string<char>; BooleanType = bool; NumberIntegerType = long int; NumberUnsignedType = long unsigned int; NumberFloatType = double; AllocatorType = std::allocator; JSONSerializer = nlohmann::adl_serializer; BinaryType = std::vector<unsigned char>]’
/home/williamtalbot/temp/eigenjsontest/eigenjsontest.cpp:42:24:   required from here
/usr/local/include/nlohmann/json.hpp:4143:25: error: invalid use of void expression
         j.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
/usr/local/include/nlohmann/json.hpp: In instantiation of ‘void nlohmann::detail::from_json_array_impl(const BasicJsonType&, ConstructibleArrayType&, nlohmann::detail::priority_tag<0>) [with BasicJsonType = nlohmann::basic_json<>; ConstructibleArrayType = Eigen::Matrix<float, 4, 4>]’:
/usr/local/include/nlohmann/json.hpp:3648:25:   required from ‘decltype (((nlohmann::detail::from_json_array_impl(j, arr, nlohmann::detail::priority_tag<3>{}), j.get<typename ConstructibleArrayType::value_type>()), void())) nlohmann::detail::from_json(const BasicJsonType&, ConstructibleArrayType&) [with BasicJsonType = nlohmann::basic_json<>; ConstructibleArrayType = Eigen::Matrix<float, 4, 4>; typename std::enable_if<((((nlohmann::detail::is_constructible_array_type<BasicJsonType, ConstructibleArrayType>::value && (! nlohmann::detail::is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value)) && (! nlohmann::detail::is_constructible_string_type<BasicJsonType, ConstructibleStringType>::value)) && (! std::is_same<ConstructibleArrayType, typename BasicJsonType::binary_t>::value)) && (! nlohmann::detail::is_basic_json<T>::value)), int>::type <anonymous> = 0; decltype (((nlohmann::detail::from_json_array_impl(j, arr, nlohmann::detail::priority_tag<3>{}), j.get<typename ConstructibleArrayType::value_type>()), void())) = void; typename ConstructibleArrayType::value_type = float]’
/usr/local/include/nlohmann/json.hpp:3791:25:   required from ‘decltype ((nlohmann::detail::from_json(j, val), void())) nlohmann::detail::from_json_fn::operator()(const BasicJsonType&, T&) const [with BasicJsonType = nlohmann::basic_json<>; T = Eigen::Matrix<float, 4, 4>; decltype ((nlohmann::detail::from_json(j, val), void())) = void]’
/usr/local/include/nlohmann/json.hpp:4386:30:   required from ‘static decltype ((nlohmann::{anonymous}::from_json(forward<BasicJsonType>(j), val), void())) nlohmann::adl_serializer<T, SFINAE>::from_json(BasicJsonType&&, ValueType&) [with BasicJsonType = const nlohmann::basic_json<>&; ValueType = Eigen::Matrix<float, 4, 4>; <template-parameter-1-1> = Eigen::Matrix<float, 4, 4>; <template-parameter-1-2> = void; decltype ((nlohmann::{anonymous}::from_json(forward<BasicJsonType>(j), val), void())) = void]’
/usr/local/include/nlohmann/json.hpp:19421:45:   required from ‘ValueType nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType>::get() const [with ValueTypeCV = Eigen::Matrix<float, 4, 4>; ValueType = Eigen::Matrix<float, 4, 4>; typename std::enable_if<(((! nlohmann::detail::is_basic_json<U>::value) && nlohmann::detail::has_from_json<nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType>, ValueType>::value) && (! nlohmann::detail::has_non_default_from_json<nlohmann::basic_json<ObjectType, ArrayType, StringType, BooleanType, NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType>, ValueType>::value)), int>::type <anonymous> = 0; ObjectType = std::map; ArrayType = std::vector; StringType = std::__cxx11::basic_string<char>; BooleanType = bool; NumberIntegerType = long int; NumberUnsignedType = long unsigned int; NumberFloatType = double; AllocatorType = std::allocator; JSONSerializer = nlohmann::adl_serializer; BinaryType = std::vector<unsigned char>]’
/home/williamtalbot/temp/eigenjsontest/eigenjsontest.cpp:43:38:   required from here
/usr/local/include/nlohmann/json.hpp:3619:42: error: invalid use of void expression
         j.begin(), j.end(), std::inserter(ret, end(ret)),

If I static cast to the base class, to_json works (although this is obviously not a solution):

    nlohmann::json j = static_cast<Eigen::MatrixBase<Eigen::Matrix4f>&>(m);
    std::cerr << j.dump() << std::endl;

yeilds [[1.0,2.0,3.0,4.0],[11.0,12.0,13.0,14.0],[21.0,22.0,23.0,24.0],[31.0,32.0,33.0,34.0]] as it should.

I am stumped. Has this template deduction issue arisen before? Any ideas what I might be doing wrong?

Setup:

  • JSON was installed from the develop branch at commit 6471a63 from mid-2021, which is part of version 3.9.1

Metadata

Metadata

Assignees

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions