Why doesn't SFINAE work across multiple inheritance?
Asked Answered
B

1

5

Here's the code:

#include <utility>
#include <type_traits>

template <class T>
class ClassWithDisabledFoo
{
public:
    template <class U = T, std::enable_if_t<std::is_integral<U>::value, int> = 0>
    void foo(){}
};

class ClassWithFoo
{
public:
    void foo(){}
};

class Derived: public ClassWithFoo, public ClassWithDisabledFoo<double>
{

};

void test()
{
    Derived d;
    d.foo();
}


At the point of calling d.foo(), both clang and gcc say that the call to foo is ambiguous, despite ClassWithDisabledFoo::foo being disabled by enable_if. If I move the foo definition from ClassWithFoo to Derived, the code compiles.

Why doesn't this work and how can I make it work?

Burne answered 4/12, 2019 at 14:19 Comment(0)
G
9

There is no overload resolution happening in your example, so the SFINAE doesn't matter.

The key here is that name lookup happens before overload resolution. Name lookup finds an overload set, and then overload resolution is performed on the set that was found.

The name has to map to a single set inside a single object type. In your case, the same name maps to members of two different sub-objects, so the name lookup is ambiguous. An immediate solution is to add using declarations:

class Derived: public ClassWithFoo, public ClassWithDisabledFoo<double>
{
    using ClassWithFoo::foo;
    using ClassWithDisabledFoo::foo;
};

This introduces the name into Derived, where the declaration can now be unambiguously found to refer to an overload set composed of the members we brought in.

Galligaskins answered 4/12, 2019 at 14:30 Comment(5)
Thank you. Sadly, this makes sense.Burne
Hm. I wonder why if all viable candidates are from the same source, they would care about the non-viable ones from elsewhere. Does it have any (safety?)-advantage?Roley
Bonus question: why exactly those two names can't be a part of an overload set? Or in other words, why can't the ambiguity check be performed after overload resolution? Is there a particular reason this was standardised as such, or is it just one of those random C++ quirks that we have to live with?Burne
@Burne - A base class can change. If it introduces a function that's a better match in overload resolution then in the current approach things don't compile. If instead we did overload resolution anyway, the new overload would get picked. So the code would build, but behave differently without notice. That would be bad.Galligaskins
@Roley - The issue is with potentially viable and better(implicit conversion-wise) overloads sneaking in.Galligaskins

© 2022 - 2024 — McMap. All rights reserved.