5

Consider the following code:

template <class...>
using void_t = void;

template <class T>
void bar(T){}

template <class T>
void bar(T, void_t<decltype(std::declval<T>().foo())>* = 0) {}

struct A { void foo(); } a;
bar(a); // gives a compiler error on ambiguous call

So the question is, why are these overloads ambiguous? Why is the second overload not considered to be more restrictive, more specialized than second one by the compiler?

Bikineev
  • 1,685
  • 15
  • 20
  • 4
    What makes it *more specialized*? Both versions can be called with no conversions, hence it does not know which one to call. – NathanOliver Sep 14 '16 at 11:37
  • 2
    Maybe it's worth mentioning that at the point of overload resolution, we no longer care which is more specialised. that's done in a different phase of compilation. – Richard Hodges Sep 14 '16 at 11:46
  • @RichardHodges Overload resolution is exactly where you consider partial ordering. – T.C. Sep 14 '16 at 20:37

5 Answers5

3

You are trying to use SFINAE to enforce the selection of a specific candidate in a specific case (here, the presence of a callable entity foo taking no argument inside a T lvalue). What does really happen here?

The template with the void_t is well defined for your struct A so at the moment of the call you have two valid candidates for the overload resolution. If you want to use SFINAE you have to make sure that for any given call only one overload is available. To do so you should first embed your test in a type trait. To this end you can take example on Yakk's can_apply facility which I shamelessly copy here since it fits very well with your need:

namespace details {
  // if Z<Ts...> is invalid, false_type:
  template <template<class...> class Z, class always_void, class... Ts>
  struct can_apply : std::false_type {};

  // if Z<Ts...> is valid, true_type:
  template <template<class...> class Z, class... Ts>
  struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...> : std::true_type {};
}
// alias to inject the void type where we need it for SFINAE:
template <template<class...> class Z, class... Ts>
using can_apply = details::can_apply<Z, void, Ts...>;

template <typename T>
using has_foo_t = decltype(std::declval<T>().foo());

template <typename T>
using has_foo = can_apply<has_foo_t, T>;

Now we only have to use the trait defined above in our template definitions:

// The enable_if with the negation is needed to invalidate
// this implementation when T indeed has foo().
// This is what you were missing in your original idea.
template <typename T>
std::enable_if_t<!has_foo<T>::value> bar(T) {
    std::cout << "T has no foo(void)" << std::endl;
}

template <typename T>
std::enable_if_t<has_foo<T>::value> bar(T) {
    std::cout << "T has a foo(void)" << std::endl;
}

You can see a running example on Coliru.

Community
  • 1
  • 1
Rerito
  • 5,886
  • 21
  • 47
  • much as I am impressed by can_apply, I find it a bit of a mind-bender... :) – Richard Hodges Sep 14 '16 at 12:39
  • 1
    @Bikineev Note that a similar mechanism called ["`is_detected`"](http://en.cppreference.com/w/cpp/experimental/is_detected) is in standards-track. It bundles a few more things along with `can_apply`. – Yakk - Adam Nevraumont Sep 14 '16 at 12:59
  • @RichardHodges Yeah it took me a while to get the `can_apply` trick but once I did I found it even greater :) – Rerito Sep 14 '16 at 14:58
2

one of the usual ways to do this is create a disjunction of equally-specialised templates.

#include <utility>
#include <iostream>

template<class T>
struct has_foo_impl
{
  template<class U> static auto test(U* p) -> decltype(p->foo(), void(), std::true_type()); 
  static auto test(...) -> decltype(std::false_type()); 

  using type = decltype(test((T*)nullptr));
};

template<class T> using has_foo = typename has_foo_impl<T>::type;

template <class...>
using void_t = void;

template <class T, std::enable_if_t<not has_foo<T>::value>* = nullptr>
void bar(T){
  std::cout << "not me\n";
}

template <class T, std::enable_if_t<has_foo<T>::value>* = nullptr>
void bar(T) {
  std::cout << "me\n";
}

struct A { void foo(); } a;

int main()
{
    bar(a); // "me"
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
1

None of the functions is more specialized than the other.

Anyway, you can force the choice by using two overloaded functions as it follows:

#include<utility>

template <class T>
void bar(char, T){}

template <class T>
auto bar(int, T) -> decltype(std::declval<T>().foo(), void()) {}

struct A { void foo(); } a;

int main() {
    bar(0, a);
}

For 0 is an int, bar(int, T) is tried for first. Because of sfinae, it could be discarded if T has not the foo member function. Anyway, 0 can be cast to char, thus bar(char, T) is chosen in that case.
If it happens that both the functions are viable solution, bar(int, T) is invoked for it doesn't require any implicit cast and the call is no longer ambiguities.

skypjack
  • 49,335
  • 19
  • 95
  • 187
1

this is other solution that compiles with g++:

//g++  4.9.3

#include <iostream>

template <class T>
    void bar(T)
{std::cout << "not foo boo...\n";}

template <class T >
    void bar( decltype(std::declval<T>().foo(),T())) 
{std::cout << "foo\n";}

template <class T >
    void bar( decltype(std::declval<T>().boo(),T())) 
{std::cout << "boo\n";}

struct Foo { void foo(); } f;

struct Boo {void boo(); } b;

struct NotFooBoo { } n;
template <class T>
    auto func(T&& t)
{
    return bar<typename std::decay<T>::type>(std::forward<T>(t));
}
int main()
{
    func(f);
    func(b);
    func(n);
}
rahnema1
  • 15,264
  • 3
  • 15
  • 27
  • The same technique is presented by skypjack. However, what if I did call bar with a `struct BothFooBoo : public Foo, Boo {};` argument? – Rerito Sep 14 '16 at 15:03
  • @Rerito I cannot see any similarity with skypjak except that both used decltype with two expressions separated by comma! the answer is based on assumptions of the questions to differentiate between classes that have foo and others instead of have both functions. you may ask your question as new thread to get proper answer :) – rahnema1 Sep 14 '16 at 16:15
  • Yeah right he embedded it in the trailing return type but its still the same. As for my second comment I was just pointing out a design flaw in your answer: a type that has both foo and boo would yield an ambiguous call. – Rerito Sep 14 '16 at 16:18
  • @Rerito you are insisting on your wrong opinion, please ask skypjak if both answers are similar. I think my answer is sufficient (and may be the best) for current question, although you are right that in situations that you noted leads to problems – rahnema1 Sep 14 '16 at 16:35
  • You both trigger SFINAE using decltype with two coma separated expressions. How is that _not_ the same technique? – Rerito Sep 14 '16 at 17:31
  • @Rerito I think it is better to finish the discussion because it is not useful. If there is any words that you want to say please explicitly say that. As my last word I say your answer to the question is admirable. – rahnema1 Sep 14 '16 at 18:36
-1

Because you are using default value for overloaded function, so compiler doesn't know which function do you want to use.

void bar(T, void_t<decltype(std::declval<T>().foo())>* = 0)

without

= 0 

it works fine.

/EDIT

template <class...>
using void_t = void;

template <class T>
void bar(T)
{}

template <class T>
void bar(T, void_t<decltype(std::declval<T>().foo())>*)
{}

struct A
{
    void foo();
} a;

int main()
{
    bar(a);
    return 0;
}

It works.

The program '[7464] Project3.exe' has exited with code 0 (0x0).
bitzz
  • 57
  • 5
  • how come it works fine? I can't call bar with single argument any longer, and that's what I actually need – Bikineev Sep 14 '16 at 11:46
  • 1
    The following comment has been added after your edit: sure it works, but it calls first overload, not second one, as intended – Bikineev Sep 14 '16 at 12:08