JuliaInterop / libcxxwrap-julia

C++ library for backing CxxWrap.jl
Other
79 stars 42 forks source link

Building against libcxxwrap-julia require definition of comparison operators between incompatible types #173

Open afossa opened 1 day ago

afossa commented 1 day ago

I am building a Julia interface to dace using libcxxwrap-julia and CxxWrap, and I recently came across a compilation error that requires me to define the "less than" operator between types for which is not defined, i.e. DACE::DA and DACE::AlgebraicMatrix<DACE::DA>.

Here is the output I obtain when trying to build the wrapper on Ubuntu 22.04 LTS: build.log config.log

I started obtaining this error after defining the comparison operators ==,!=,<,>,<=,>= between DACE::DA objects and double in interfaces/cxx/include/dace/DA.h

    friend inline bool DACE_API operator==(const DA &da1, const DA &da2) { return da1.cons() == da2.cons(); };  //!< Equality comparison
    friend inline bool DACE_API operator==(const DA &da, const double c) { return da.cons() == c; };            //!< Equality comparison
    friend inline bool DACE_API operator==(const double c, const DA &da) { return c == da.cons(); };            //!< Equality comparison

    friend inline bool DACE_API operator!=(const DA &da1, const DA &da2) { return da1.cons() != da2.cons(); };  //!< Inequality comparison
    friend inline bool DACE_API operator!=(const DA &da, const double c) { return da.cons() != c; };            //!< Inequality comparison
    friend inline bool DACE_API operator!=(const double c, const DA &da) { return c != da.cons(); };            //!< Inequality comparison

    friend inline bool DACE_API operator<(const DA &da1, const DA &da2) { return da1.cons() < da2.cons(); };    //!< Less than comparison
    friend inline bool DACE_API operator<(const DA &da, const double c) { return da.cons() < c; };              //!< Less than comparison
    friend inline bool DACE_API operator<(const double c, const DA &da) { return c < da.cons(); };              //!< Less than comparison

    friend inline bool DACE_API operator>(const DA &da1, const DA &da2) { return da1.cons() > da2.cons(); };    //!< Greater than comparison
    friend inline bool DACE_API operator>(const DA &da, const double c) { return da.cons() > c; };              //!< Greater than comparison
    friend inline bool DACE_API operator>(const double c, const DA &da) { return c > da.cons(); };              //!< Greater than comparison

    friend inline bool DACE_API operator<=(const DA &da1, const DA &da2) { return da1.cons() <= da2.cons(); };  //!< Less than or equal comparison
    friend inline bool DACE_API operator<=(const DA &da, const double c) { return da.cons() <= c; };            //!< Less than or equal comparison
    friend inline bool DACE_API operator<=(const double c, const DA &da) { return c <= da.cons(); };            //!< Less than or equal comparison

    friend inline bool DACE_API operator>=(const DA &da1, const DA &da2) { return da1.cons() >= da2.cons(); };  //!< Greater than or equal comparison
    friend inline bool DACE_API operator>=(const DA &da, const double c) { return da.cons() >= c; };            //!< Greater than or equal comparison
    friend inline bool DACE_API operator>=(const double c, const DA &da) { return c >= da.cons(); };            //!< Greater than or equal comparison

I am also able to overcome it by adding these dummy definitions at the top of the interface interfaces/julia/dace_julia.cxx

namespace DACE {
    inline bool DACE_API operator<(const AlgebraicMatrix<DA> &mat, const DA &da) { throw std::runtime_error("Comparison between DA and AlgebraicMatrix not defined"); };
    inline bool DACE_API operator<(const DA &da, const AlgebraicMatrix<DA> &mat) { throw std::runtime_error("Comparison between DA and AlgebraicMatrix not defined"); };
    inline bool DACE_API operator<(const AlgebraicMatrix<DA> &mt1, const AlgebraicMatrix<DA> &mt2) { throw std::runtime_error("Comparison between two AlgebraicMatrix not defined"); };
}

but this is really a workaround that shouldn't be there.

More surprisingly, I do not encounter the same error with DACE::AlgebraicVector<DA>, which is wrapped pretty much in the same way as DACE::AlgebraicMatrix<DA> is. Building the library without the Julia wrapper bits also works, so it is really a matter of how it interacts with libcxxwrap-julia.

Do you have any insight on the root cause of this error? Thanks!

barche commented 1 day ago

Is this against the latest libcxxwrap-julia (main branch)? It is probably related to the added support for more STL containters, maybe this check isn't working:

https://github.com/JuliaInterop/libcxxwrap-julia/blob/587f9feb6b64baa512856af2d508fec1f63583bc/include/jlcxx/stl.hpp#L657

afossa commented 1 day ago

It happens both against main and against the latest release v0.13.2. But yes, it seems related to the instantiation of STL containers. The logs I attached are actually against the latest release and not main.

barche commented 1 day ago

OK, if you don't need sets or maps then you can comment out these lines in apply_stl as a temporary workaround. I was already working on refactoring this part of the code, so only the part of the STL that is requested gets wrapped, i.e. if you use a std::vector with your type, only vector will be wrapped and not all supported containers.

afossa commented 14 hours ago

Thank you for the hint!

I did a couple of other tests, and it seems that the check you mentioned before does work in most of the cases, but it gets "confused" when the type to which the STL is applied is itself a kind of "container". What I have observed is the following:

I have my wrapped type DA on which I explicitly apply the STL, i.e.:

jlcxx::stl::apply_stl<DA>(mod)

Then I have the parametric type AlgebraicMatrix<T> which is defined for T being a DA or a double as:

mod.add_type<jlcxx::Parametric<jlcxx::TypeVar<1>>>("AlgebraicMatrix", jlcxx::julia_type("AbstractMatrix", "Base"))
    .apply<AlgebraicMatrix<DA>, AlgebraicMatrix<double>>([](auto wrapped) {
...
});

The type DA is used several times as the element type of std::vectors, while AlgebraicMatrix<T> is used as element of an STL container (a vector) only once:

mod.method("hessian", [](const AlgebraicVector<DA>& vec)->std::vector<AlgebraicMatrix<DA>> { return vec.hessian(); });

Moreover, the DA type defines the "less than" and the other comparison operators between DAs, and between a DA and a double.

To get a compiler error, I need both the hessian() method being wrapped, AND the comparison operators being defined for DAs. If I remove either one of the two, the wrapper is compiled just fine, and I can call it from Julia.