Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ partial ordering for a function with default argument

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?

like image 589
Bikineev Avatar asked Sep 14 '16 11:09

Bikineev


4 Answers

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.

like image 119
Rerito Avatar answered Sep 30 '22 01:09

Rerito


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"
}
like image 23
Richard Hodges Avatar answered Sep 30 '22 01:09

Richard Hodges


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.

like image 30
skypjack Avatar answered Sep 30 '22 00:09

skypjack


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);
}
like image 36
rahnema1 Avatar answered Sep 30 '22 01:09

rahnema1