Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ambiguous call not avoided by SFINAE

Compiling this code :

#include <iostream>


template <int N>
struct TestClass {
    template <int N2, typename std::enable_if<N2 == N, int>::type = 0>
    void doAction() { std::cout << "TestClass::doAction<" << N << ">();\n"; }
};

struct HostClass : public TestClass<1>, public TestClass<2> {
};


int main(int argc, const char * argv[]) {
    HostClass hostClass;

    hostClass.doAction<1>();
    hostClass.doAction<2>();

    return 0;
}

leads to an ambiguous call error because doAction is both in TestClass<1> and TestClass<2> parent classes.

main.cpp:33:15: Member 'doAction' found in multiple base classes of different types

But std::enable_if would not disable this ambiguity ?

EDIT:

I think the real reason to this ambiguity is the same than in this question :

Why do multiple-inherited functions with same name but different signatures not get treated as overloaded functions?

The ambiguity can be resolved as shown in the answer with the using keyword :

#include <iostream>


template <int N>
struct TestClass {
    template <int N2, typename std::enable_if<N2 == N, int>::type = 0>
    void doAction() { std::cout << "TestClass::doAction<" << N << ">();\n"; }
};

struct HostClass : public TestClass<1>, public TestClass<2> {
    using TestClass<1>::doAction;
    using TestClass<2>::doAction;
};

int main(int argc, const char * argv[]) {
    HostClass hostClass;

    hostClass.doAction<1>();    // OK, compile
    hostClass.doAction<2>();    // OK, compile
    //hostClass.doAction<3>();  // OK, doesn't compile : "candidate template ignored: disabled by 'enable_if' [with N2 = 3]"

    return 0;
}

I don't know if it was what @skypjack answer meant but I let it anyway for its alternative method.

like image 886
Johnmph Avatar asked Nov 12 '16 20:11

Johnmph


1 Answers

It would (let me say) drop one of the two functions after the substitution.
Anyway, first of all the compiler has to decide which function you intend to use when you invoke it as doAction<1>, then it can go ahead with the substitution and eventually toss away the chosen function because of sfinae.
At the point of the invocation, both of them are valid candidates and the call is actually ambiguous.

Note that, as suggested by @Peregring-lk in the comments, TestClass<1>::doAction and TestClass<2>::doAction are two different functions placed in different namespaces, they are not overloads of the same function.
This is actually a common source of misunderstandings.


You can easily solve the issue as it follows:

#include <iostream>

template <int N>
struct TestClass {
    void doAction() { std::cout << "TestClass::doAction<" << N << ">();\n"; }
};

struct HostClass : public TestClass<1>, public TestClass<2> {
    template<int N>
    void doAction() { return TestClass<N>::doAction(); }
};


int main(int argc, const char * argv[]) {
    HostClass hostClass;

    hostClass.doAction<1>();
    hostClass.doAction<2>();

    return 0;
}
like image 152
skypjack Avatar answered Sep 22 '22 00:09

skypjack