When overloading a function with multiple inheritance, GCC says the call is ambiguous, but Clang and MSVC do not

I use this library of options: https://github.com/cbeck88/strict-variant . It provides a class similar to std::variant and boost::variant . Given this struct :

 struct S { explicit S(double) {} }; 

I want to do this:

 strict_variant::variant<double, S> v = 2.0; 

This works with Clang 5.0.1 and MSVC 12.19.25831.00, but does not compile with GCC 7.2.1.

I looked at the library code and reduced the problem to this:

 #include <iostream> struct S { constexpr S() {} constexpr explicit S(double) {} }; template<unsigned i> struct init_helper; template<> struct init_helper<0> { using type = double; }; template<> struct init_helper<1> { using type = S; }; template<unsigned i> struct initializer_leaf { using target_type = typename init_helper<i>::type; constexpr unsigned operator()(target_type) const { return i; } }; struct initializer : initializer_leaf<0>, initializer_leaf<1> { }; int main() { std::cout << initializer()(double{}) << " = double" << '\n'; std::cout << initializer()(S{}) << " = S" << '\n'; return 0; } 

with exit

 0 = double 1 = S 

GCC says:

 strict_variant_test.cpp: In function 'int main()': strict_variant_test.cpp:29:37: error: request for member 'operator()' is ambiguous std::cout << initializer()(double{}) << " = double" << '\n'; ^ strict_variant_test.cpp:17:21: note: candidates are: constexpr unsigned int initializer_leaf<i>::operator()(initializer_leaf<i>::target_type) const [with unsigned int i = 1; initializer_leaf<i>::target_type = S] constexpr unsigned operator()(target_type) const ^~~~~~~~ strict_variant_test.cpp:17:21: note: constexpr unsigned int initializer_leaf<i>::operator()(initializer_leaf<i>::target_type) const [with unsigned int i = 0; initializer_leaf<i>::target_type = double] strict_variant_test.cpp:30:32: error: request for member 'operator()' is ambiguous std::cout << initializer()(S{}) << " = S" << '\n'; ^ strict_variant_test.cpp:17:21: note: candidates are: constexpr unsigned int initializer_leaf<i>::operator()(initializer_leaf<i>::target_type) const [with unsigned int i = 1; initializer_leaf<i>::target_type = S] constexpr unsigned operator()(target_type) const ^~~~~~~~ strict_variant_test.cpp:17:21: note: constexpr unsigned int initializer_leaf<i>::operator()(initializer_leaf<i>::target_type) const [with unsigned int i = 0; initializer_leaf<i>::target_type = double] 

But it works with GCC (and still Clang and MSVC) when I change the initializer definition to this:

 struct initializer { constexpr unsigned operator()(double) const { return 0; } constexpr unsigned operator()(S) const { return 1; } }; 

My understanding of C ++ says this is equivalent, so I assume it is a bug in GCC, but I often run into problems when the standard says amazing things and my assumption is wrong. So my question is: whose fault is this? GCC has an error, Clang and MSVC have an error, or is this an interpretation of undefined / unspecified code, so are all compilers right? If the code is incorrect, how can I fix it?

+7
c ++ g ++ clang ++ c ++ 17
source share
1 answer

This is actually a clang error.

The rule of thumb is that names in different areas are not overloaded. Here is an example:

 template <typename T> class Base { public: void foo(T ) { } }; template <typename... Ts> struct Derived: Base<Ts>... {}; int main() { Derived<int, double>().foo(0); // error } 

This should be a mistake, because the rules for finding class members claim that basically only one base class can contain a given name. If several base classes have the same name, the search is ambiguous. Here it is allowed to cast the names of the base class into a derived class using the declaration-declaration. In C ++ 17, that using declarations can be a package extension, which makes this problem much easier:

 template <typename T> class Base { public: void foo(T ) { } }; template <typename... Ts> struct Derived: Base<Ts>... { using Base<Ts>::foo...; }; int main() { Derived<int, double>().foo(0); // ok! calls Base<int>::foo } 

For a specific library, this code :

 template <typename T, unsigned... us> struct initializer_base<T, mpl::ulist<us...>> : initializer_leaf<T, us>... { static_assert(sizeof...(us) > 0, "All value types were inelligible!"); }; 

should look like this:

 template <typename T, unsigned... us> struct initializer_base<T, mpl::ulist<us...>> : initializer_leaf<T, us>... { static_assert(sizeof...(us) > 0, "All value types were inelligible!"); using initializer_leaf<T, us>::operator()...; // (*) <== }; 

(although I suppose the library is targeting C ++ 11, so I presented a fix compatible with C ++ 11 for it ... it's a little more verbose).

+7
source share

All Articles