I don't mean to code dump but this is genuinely the smallest reproducible example of this I could create even after removing all the logic to make it clearer.
Essentially I'm trying to implement my own version of some of the base types in C++ to mimic dynamic typing by storing the types in a boost::variant called Values and using boost::static_visitors to perform operations on the Value variant. One operation I'm trying to implement is the the not equals operator, and I have created a visitor called Not_Equal to achieve this. The Not_Equal operator uses SFINAE with a low_priority and high_priority struct to determine if the two types used in the operation are allowed.
The types in the Values variant are: {SpecialInt, SpecialBoolean, std::shared_ptr<SeriesInt>, std::shared_ptr<SeriesBoolean>}.
The reason why the SeriesInt and SeriesBoolean are smart pointers is because they store a lot of information in my real version so copying them will be expensive.
The accepted != operators are as follows:
SpecialInt != SpecialInt
SpecialBoolean != SpecialBoolean
SeriesInt != SpecialInt
SeriesInt != SeriesInt
SeriesBoolean != SeriesBoolean
as represented by the operator overloads in each class.
#include <memory>
#include <boost/variant/static_visitor.hpp>
#include <boost/variant.hpp>
class SpecialBoolean {
public:
SpecialBoolean(bool val) {} //removing this line fixes it
SpecialBoolean() {}
SpecialBoolean operator!= (const SpecialBoolean& rhs) const {
return *this;
}
};
class SpecialInt {
public:
SpecialInt(float val) {} //removing this line fixes it
SpecialInt() {}
SpecialBoolean operator!= (const SpecialInt& rhs) const {
return SpecialBoolean();
}
};
class SeriesBoolean {
public:
SeriesBoolean() {}
std::shared_ptr<SeriesBoolean> operator!= (const SpecialBoolean& rhs) const {
return std::make_shared<SeriesBoolean>();
}
std::shared_ptr<SeriesBoolean> operator!= (const SeriesBoolean& rhs) const {
return std::make_shared<SeriesBoolean>();
}
};
class SeriesInt {
public:
SeriesInt() {}
std::shared_ptr<SeriesBoolean> operator!= (const SpecialInt& rhs) const {
return std::make_shared<SeriesBoolean>();
}
std::shared_ptr<SeriesBoolean> operator!= (const SeriesInt& rhs) const {
return std::make_shared<SeriesBoolean>();
}
};
typedef boost::variant <SpecialInt, SpecialBoolean, std::shared_ptr<SeriesInt>, std::shared_ptr<SeriesBoolean> > Values;
struct low_priority {};
struct high_priority : low_priority {};
struct Not_Equal : public boost::static_visitor<Values> {
auto operator() (Values const& a, Values const& b) const {
return boost::apply_visitor(*this, a, b);
}
template <typename T, typename U>
auto operator() (high_priority, T a, U b) const -> decltype(Values(a != b)) {
return a != b; // problem here
}
template <typename T, typename U>
auto operator() (high_priority, std::shared_ptr<T> a, std::shared_ptr<U> b) const -> decltype(Values(*a != *b)) {
return *a != *b;
}
template <typename T, typename U>
auto operator() (high_priority, std::shared_ptr<T> a, U b) const -> decltype(Values(*a != b)) {
return *a != b;
}
template <typename T, typename U>
Values operator() (low_priority, T, U) const {
throw std::runtime_error("Incompatible arguments");
}
template <typename T, typename U>
Values operator() (T a, U b) const {
return (*this)(high_priority{}, a, b);
}
};
The problem occurs in the visitor at line return a != b; , where the operator types are not shared_ptr's and therefore either SpecialInt or SpecialBoolean which causes the errors:
Error C2446 '!=': no conversion from 'SeriesInt *' to 'SeriesBoolean *'
Error C2446 '!=': no conversion from 'SeriesBoolean *' to 'SeriesInt *'
I don't understand what it has to do with SeriesBoolean* or SeriesInt* since it can only accept types of SpecialInt and SpecialBoolean, but I've noticed that when I remove the constructors which take a argument in SpecialInt and SpecialBoolean that the code compiles and runs as normal. I need those constructors to load values into the classes (logic removed) so my question is why am I getting these errors and how can I fix this?
The contructors for your types lead to ambiguous variant initializers.
If you can, consider making them explicit.
Also, the decltype return types don't really make sense since the visitor returns
Valuesby definition.This overload
matches ALL combinations.
operator !=is /not defined/ in such cases. Did you mean to do:Now you need the following overload as well:
Otherwise two identical shared_pointer argument types would be ambiguous.
This overload seems wrong:
This will obviously lead to problems because it will e.g. compare
SeriesInttoSeriesBoolwhich isn't implemented. Since it's not on your list, drop it.Likewise, since
also matches e.g [ T = SeriesInt, U = SpecialBoolean ], it will not compile.
SIMPLIFY!
I would basically run down the list of supported overloads and just explicitly implement them. I'll use the above templates only for the cases that are 1:1.
Live Demo
Live On Coliru
BONUS
Also, I think you should encapsulate the optimization of using shared_ptr<>, eliminating all your special cases.
This simplifies all of the above to:
Here's a complete demo with testcases for that Live On Coliru
Printing