4

I have some code which, very much simplified, looks somewhat like this:

#include <iostream>
#include <type_traits>

namespace X {
    struct Foo {int x;};
    struct Bar {int x;};

    template <typename T , typename = typename std::enable_if<
                                                              std::is_same<decltype(T::x),int>::value
                                                             >::type>
    std::ostream & operator<<(std::ostream & os, const T&) {
        return os;
    }
}

namespace Y {
    struct Faa : X::Foo {int y;};
    struct Baz {int x; int y;};

    template <typename T , typename = typename std::enable_if<
                                                              std::is_same<decltype(T::x),int>::value && 
                                                              std::is_same<decltype(T::y),int>::value
                                                             >::type>
    std::ostream & operator<<(std::ostream & os, const T&) {
        return os;
    }
}


int main() {
    // Everything is ok
    X::Foo x;
    std::cout << x;

    Y::Baz k;
    std::cout << k;

    // Problems..
    Y::Faa y;

    // std::cout << y; // <--operator is ambiguous
    Y::operator<<(std::cout, y);

    return 0;
}

Is there any way to avoid the ambiguous operator for Y::Faa and having to manually specify Y::operator<<? If not, why?

Svalorzen
  • 5,353
  • 3
  • 30
  • 54
  • The restriction imposed via `enable_if` seems quite weak (too many types are allowed). Can you refine them e.g. by using a type trait? – dyp Sep 23 '14 at 14:27
  • In my actual code I have made an actual trait which checks the existence of various member methods, but the idea is the same. I don't think this is yet a problem here though. – Svalorzen Sep 23 '14 at 14:39
  • I rather thought of something like a list of allowed types; something that lets you "determine" which types are (intended as) direct members of a namespace. From those, you can then select those with a member named `x`. – dyp Sep 23 '14 at 14:43
  • @dyp The problem is that these functions are part of a library and the user may want to call them with custom types. – Svalorzen Sep 23 '14 at 14:58

1 Answers1

3

Two functions have a conflict because conditions on their arguments have non-empty intersection (actually, 1st supersedes 2nd). Function overloading works only if signatures are different. So, to solve this we have 2 options:

Change conditions so that they have empty intersection (manually forbid having y field by adding && !sfinae_has_member_y<T>::value condition to the 1st enable_if)

template<typename T>
struct sfinae_has_member_y {
    static int has(...);
    template<typename U = T, typename = decltype(U::y)>
    static char has(const U& value);
    enum { value = sizeof(char) == sizeof(has(std::declval<T>())) };
};

OR use another C++ feature that supports arguments overlapping, like struct/class template specialization. If you replace bool with int, other fields may be added too:

template<typename T, bool>
struct Outputter {
};
template<typename T>
struct Outputter<T, false> {
    static std::ostream & output(std::ostream & os, const T&) {
        os << "x";
        return os;
    }
};
template<typename T>
struct Outputter<T, true> {
    static std::ostream & output(std::ostream & os, const T&) {
        os << "y";
        return os;
    }
};

template<typename T, typename = std::enable_if_t<std::is_same<decltype(T::x), int>::value>>
std::ostream & operator<<(std::ostream & os, const T& a) {
    return Outputter<T, sfinae_has_member_y<T>::value>::output(os, a);
}
Mykola Bohdiuk
  • 1,307
  • 12
  • 16