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_visitor
s 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 Values
by definition.
This overload
template <typename T, typename U>
Values operator()(high_priority, T a, U b) const {
return a != b; // problem here
}
matches ALL combinations. operator !=
is /not defined/ in such cases. Did you mean to do:
template <typename T>
Values operator()(high_priority, T const& a, T const& b) const {
return a != b; // problem here
}
Now you need the following overload as well:
template <typename T>
Values operator()(high_priority, std::shared_ptr<T> const& a, std::shared_ptr<T> const& b) const {
return *a != *b;
}
Otherwise two identical shared_pointer argument types would be ambiguous.
This overload seems wrong:
template <typename T, typename U>
Values operator()(high_priority, std::shared_ptr<T> const& a, std::shared_ptr<U> const& b) const {
return *a != *b;
}
This will obviously lead to problems because it will e.g. compare SeriesInt
to SeriesBool
which isn't implemented. Since it's not on your list, drop it.
Likewise, since
template <typename T, typename U>
Values operator()(high_priority, std::shared_ptr<T> const& a, U const& b) const {
return *a != b;
}
also matches e.g [ T = SeriesInt, U = SpecialBoolean ], it will not compile.
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.
Note that consistently (!) taking args by const& make the execution a lot more efficient especially for the shared pointers.
struct Not_Equal : boost::static_visitor<Values> {
Values operator()(Values const& a, Values const& b) const {
return boost::apply_visitor(*this, a, b);
}
// SpecialInt != SpecialInt
// SpecialBoolean != SpecialBoolean
template <typename T>
Values operator()(T const& a, T const& b) const {
return a != b;
}
// SeriesInt != SeriesInt
// SeriesBoolean != SeriesBoolean
template <typename T>
Values operator()(std::shared_ptr<T> const& a, std::shared_ptr<T> const& b) const {
return *a != *b;
}
// SeriesInt != SpecialInt
Values operator()(std::shared_ptr<SeriesInt> const& a, SpecialInt const& b) const {
return *a != b;
}
template <typename... T>
Values operator()(T const&...) const {
throw std::runtime_error("Incompatible arguments");
}
};
#include <boost/variant.hpp>
#include <boost/variant/static_visitor.hpp>
#include <memory>
struct SpecialBoolean {
explicit SpecialBoolean(bool /*val*/ = false) {}
SpecialBoolean operator!=(const SpecialBoolean& /*rhs*/) const { return *this; }
};
struct SpecialInt {
explicit SpecialInt(float /*val*/ = 0) {}
SpecialBoolean operator!=(const SpecialInt& /*rhs*/) const {
return SpecialBoolean();
}
};
struct SeriesBoolean {
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>();
}
};
struct SeriesInt {
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 Not_Equal : boost::static_visitor<Values> {
Values operator()(Values const& a, Values const& b) const {
return boost::apply_visitor(*this, a, b);
}
// SpecialInt != SpecialInt
// SpecialBoolean != SpecialBoolean
template <typename T>
Values operator()(T const& a, T const& b) const {
return a != b;
}
// SeriesInt != SeriesInt
// SeriesBoolean != SeriesBoolean
template <typename T>
Values operator()(std::shared_ptr<T> const& a, std::shared_ptr<T> const& b) const {
return *a != *b;
}
// SeriesInt != SpecialInt
Values operator()(std::shared_ptr<SeriesInt> const& a, SpecialInt const& b) const {
return *a != b;
}
template <typename... T>
Values operator()(T const&...) const {
throw std::runtime_error("Incompatible arguments");
}
};
int main() {
}
Also, I think you should encapsulate the optimization of using shared_ptr<>, eliminating all your special cases.
This simplifies all of the above to:
struct Not_Equal : boost::static_visitor<Values> {
Values operator()(Values const& a, Values const& b) const {
return boost::apply_visitor(*this, a, b);
}
template <typename T>
Values operator()(T const& a, T const& b) const { return a != b; }
Values operator()(SeriesInt const& a, SpecialInt const& b) const { return a != b; }
Values operator()(SeriesBoolean const& a, SpecialBoolean const& b) const { return a != b; }
template <typename... T>
Values operator()(T const&...) const {
throw std::runtime_error("Incompatible arguments");
}
};
Here's a complete demo with testcases for that Live On Coliru
#include <boost/variant.hpp>
#include <boost/variant/static_visitor.hpp>
#include <memory>
#include <vector>
#include <iostream>
#include <iomanip>
struct SpecialBoolean {
explicit SpecialBoolean(bool val = false) : val(val) {}
SpecialBoolean operator!=(const SpecialBoolean& rhs) const { return SpecialBoolean{val != rhs.val}; }
private:
bool val;
friend std::ostream& operator<<(std::ostream& os, SpecialBoolean const& b) { return os << "SpecialBoolean{" << std::boolalpha << b.val << "}"; }
};
struct SpecialInt {
explicit SpecialInt(float val = false) : val(val) {}
SpecialBoolean operator!=(const SpecialInt& rhs) const { return SpecialBoolean{val != rhs.val}; }
private:
float val;
friend std::ostream& operator<<(std::ostream& os, SpecialInt const& i) { return os << "SpecialInt{" << i.val << "}"; }
};
struct SeriesBoolean {
SeriesBoolean operator!=(const SpecialBoolean& /*rhs*/) const { return {}; } // TODO
SeriesBoolean operator!=(const SeriesBoolean& /*rhs*/) const { return {}; } // TODO
private:
struct VeryLarge {
std::array<SpecialBoolean, 512> _it_is;
};
std::shared_ptr<VeryLarge> _data = std::make_shared<VeryLarge>();
friend std::ostream& operator<<(std::ostream& os, SeriesBoolean const&) { return os << "SeriesBoolean{...}"; }
};
struct SeriesInt {
SeriesBoolean operator!=(const SpecialInt& /*rhs*/) const { return {}; }
SeriesBoolean operator!=(const SeriesInt& /*rhs*/) const { return {}; }
private:
struct VeryLarge {
std::array<SpecialInt, 512> _it_is;
};
std::shared_ptr<VeryLarge> _data = std::make_shared<VeryLarge>();
friend std::ostream& operator<<(std::ostream& os, SeriesInt const&) { return os << "SeriesInt{...}"; }
};
using Values = boost::variant< SpecialInt, SpecialBoolean, SeriesInt, SeriesBoolean >;
struct Not_Equal : boost::static_visitor<Values> {
Values operator()(Values const& a, Values const& b) const {
return boost::apply_visitor(*this, a, b);
}
template <typename T>
Values operator()(T const& a, T const& b) const { return a != b; }
Values operator()(SeriesInt const& a, SpecialInt const& b) const { return a != b; }
Values operator()(SeriesBoolean const& a, SpecialBoolean const& b) const { return a != b; }
template <typename... T>
Values operator()(T const&...) const {
throw std::runtime_error("Incompatible arguments");
}
};
int main() {
Values const vv[] = {
SpecialInt(42),
SpecialInt(-314e-2),
SpecialBoolean(false),
SpecialBoolean(true),
SeriesInt(),
SeriesBoolean()
};
Not_Equal const neq;
auto col = [](auto const& v, bool right = false) -> auto& {
std::ostringstream ss; // just for quick formatting
ss << v;
if (right)
std::cout << std::right;
else
std::cout << std::left;
return std::cout << std::setw(21) << ss.str();
};
for (auto const& a: vv) for (auto const& b: vv) try {
col(a, true) << " != ";
col(b) << " --> ";
col(neq(a, b)) << "\n";
} catch(std::exception const& e) {
col(e.what()) << "\n";
}
}
Printing
SpecialInt{42} != SpecialInt{42} --> SpecialBoolean{false}
SpecialInt{42} != SpecialInt{-3.14} --> SpecialBoolean{true}
SpecialInt{42} != SpecialBoolean{false} --> Incompatible arguments
SpecialInt{42} != SpecialBoolean{true} --> Incompatible arguments
SpecialInt{42} != SeriesInt{...} --> Incompatible arguments
SpecialInt{42} != SeriesBoolean{...} --> Incompatible arguments
SpecialInt{-3.14} != SpecialInt{42} --> SpecialBoolean{true}
SpecialInt{-3.14} != SpecialInt{-3.14} --> SpecialBoolean{false}
SpecialInt{-3.14} != SpecialBoolean{false} --> Incompatible arguments
SpecialInt{-3.14} != SpecialBoolean{true} --> Incompatible arguments
SpecialInt{-3.14} != SeriesInt{...} --> Incompatible arguments
SpecialInt{-3.14} != SeriesBoolean{...} --> Incompatible arguments
SpecialBoolean{false} != SpecialInt{42} --> Incompatible arguments
SpecialBoolean{false} != SpecialInt{-3.14} --> Incompatible arguments
SpecialBoolean{false} != SpecialBoolean{false} --> SpecialBoolean{false}
SpecialBoolean{false} != SpecialBoolean{true} --> SpecialBoolean{true}
SpecialBoolean{false} != SeriesInt{...} --> Incompatible arguments
SpecialBoolean{false} != SeriesBoolean{...} --> Incompatible arguments
SpecialBoolean{true} != SpecialInt{42} --> Incompatible arguments
SpecialBoolean{true} != SpecialInt{-3.14} --> Incompatible arguments
SpecialBoolean{true} != SpecialBoolean{false} --> SpecialBoolean{true}
SpecialBoolean{true} != SpecialBoolean{true} --> SpecialBoolean{false}
SpecialBoolean{true} != SeriesInt{...} --> Incompatible arguments
SpecialBoolean{true} != SeriesBoolean{...} --> Incompatible arguments
SeriesInt{...} != SpecialInt{42} --> SeriesBoolean{...}
SeriesInt{...} != SpecialInt{-3.14} --> SeriesBoolean{...}
SeriesInt{...} != SpecialBoolean{false} --> Incompatible arguments
SeriesInt{...} != SpecialBoolean{true} --> Incompatible arguments
SeriesInt{...} != SeriesInt{...} --> SeriesBoolean{...}
SeriesInt{...} != SeriesBoolean{...} --> Incompatible arguments
SeriesBoolean{...} != SpecialInt{42} --> Incompatible arguments
SeriesBoolean{...} != SpecialInt{-3.14} --> Incompatible arguments
SeriesBoolean{...} != SpecialBoolean{false} --> SeriesBoolean{...}
SeriesBoolean{...} != SpecialBoolean{true} --> SeriesBoolean{...}
SeriesBoolean{...} != SeriesInt{...} --> Incompatible arguments
SeriesBoolean{...} != SeriesBoolean{...} --> SeriesBoolean{...}