The following code doesn't compile:
#include <boost/variant.hpp>
class A {};
class B {};
class C {};
class D {};
using v1 = boost::variant<A, B>;
using v2 = boost::variant<C, D>;
int f(v1 const&) {
    return 0;
}
int f(v2 const&) {
    return 1;
}
int main() {
    return f(A{});
}
both gcc and clang complains with:
test1.cpp: In function ‘int main()’:
test1.cpp:18:17: error: call of overloaded ‘f(A)’ is ambiguous
     return f(A{});
                 ^
test1.cpp:18:17: note: candidates are:
test1.cpp:11:5: note: int f(const v1&)
 int f(v1 const&) {
     ^
test1.cpp:14:5: note: int f(const v2&)
 int f(v2 const&) {
Given that is not possible to construct a v2 object from an A instance why the compiler gives the same priority to both functions during the overload resolution?
The problem is the constructor
template<typename T>
variant(const T&)
of boost::variant. The code below reproduces the problem without all the magic:
struct C {};
struct A {
  A(const C&) {}
  template<typename T>
  A(const T&) {}
};
struct B {
  template<typename T>
  B(const T&) {}
};
int f(const A&) {
    return 0;
}
int f(const B&) {
    return 1;
}
int main() {
  return f(C{});
}
I think the variant constructor should only be enabled if the
argument is actually convertible to the arguments, you might want to
raise this as a bug.
A pragmatic approach, short of fixing the conversion with explicit conversion with proper SFINAE (I don't think it can be done elegantly outside the Boost Variant library), could be this:
See it Live On Coliru
#include <boost/variant.hpp>
class A {};
class B {};
class C {};
class D {};
using v1 = boost::variant<A, B>;
using v2 = boost::variant<C, D>;
namespace detail {
    struct F : boost::static_visitor<int> {
        template <typename... T>
        int operator()(boost::variant<T...> const& v) const {
            return boost::apply_visitor(*this, v);
        }
        int operator()(A) const { return 0; }
        int operator()(B) const { return 0; }
        int operator()(C) const { return 1; }
        int operator()(D) const { return 1; }
    } _f;
}
template <typename T>
int f(T const& t) {
    return detail::F()(t);
}
int main() {
    std::cout <<  f(A{})   << "\n";
    std::cout <<  f(B{})   << "\n";
    std::cout <<  f(C{})   << "\n";
    std::cout <<  f(D{})   << "\n";
    std::cout <<  f(v1{})  << "\n";
    std::cout <<  f(v2{})  << "\n";
}
Prints
0
0
1
1
0
1
The assumption is that f(T) always returns the same value for the same T even if it is the member of more than one variant
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With