Fidget324
Fidget324

Reputation: 33

C++ using a template argument to resolve an overload

I'm writing a wrapper template class which can wrap an arbitrary type and imbue it with some additional semantics, but I can't figure out how to get overload resolution to work properly. The issue arises when a conversion that would ordinarily be resolved by comparing ranks of competing conversion sequences, cannot be deduced by the compiler because the type in question is a template argument, rather than a function argument. For instance,

#include <type_traits>

template <typename T> class Wrapper {
  T val;

public:
  Wrapper() = default;
  template <typename U> Wrapper(Wrapper<U> x) : val(x.val) {}
};

void foo(Wrapper<const char *>) {}
void foo(Wrapper<bool>) {}

int main() {
  Wrapper<char *> cp;
  foo(cp);
}

Here, the call to foo() is ambiguous. The desired behavior would be for the compiler to select void foo(Wrapper<const char *>), as it would if cp were instead a char * and foo were instead void foo(const char *). Is this possible?

EDIT: Thanks to everyone for the quick responses, but perhaps I should have been more clear. What I have given above is just an example. What I require is a general solution to the following question: given arbitrary types T, U, and V, suppose that C++'s built in overload resolution would prefer the conversion T -> U over T -> V. How can I then also ensure that C++ would prefer Wrapper<T> -> Wrapper<U> over Wrapper<T> -> Wrapper<V>?

I made this clarification because it seemed that the answers were specifically addressing certain aspects of overload resolution, like cv-qualifiedness, whereas I really need a general solution.

Upvotes: 2

Views: 731

Answers (4)

O&#39;Neil
O&#39;Neil

Reputation: 3849

The problem here is that both overloads have the exact same weight in the resolution because of the template.

If you want overload resolution to happen, you have to introduce overload resolution.
This can be done by adding the corresponding type as second (unused) parameter:

void foo(Wrapper<const char *>, const char *)
void foo(Wrapper<bool>,         bool)

With the help of the following alias in your wrapper:

using value_type = T;

The following foo() function can select the best overload:

template <typename W> 
void foo(W && w) {
  foo(std::forward<W>(w), typename std::remove_reference_t<W>::value_type{}); 
}

DEMO

Upvotes: 1

Pavel Shevchuk
Pavel Shevchuk

Reputation: 1

There are few things you can do:

  1. Simply prohibit construction Wrapper<bool> from Wrapper<T *>. Such things are very error-prone
  2. Use SFINAE
#include <type_traits>

template <typename T> class Wrapper {
  T val;
public:
  T getVal() const {
    return val;
  }
  Wrapper() = default;
  template <typename U,
            class = typename std::enable_if<std::is_same<typename std::remove_cv<typename std::remove_pointer<T>::type>::type,
                                                         typename std::remove_pointer<U>::type>::value>::type>
  Wrapper(Wrapper<U> x) : val(x.getVal()) {}
};

void foo(Wrapper<const char *>) {}
void foo(Wrapper<bool>) {}

int main() {
  Wrapper<char *> cp;
  foo(cp);
}

Using this you can allow only a certain set of conversions, i.e. : X *-> const X *, conversions between integer types, etc.

UPDATE: Unfortunately, it seems that you cannot imitate the standard overload resolution rules, because all you can use is the conversion operator, and in terms of overload resolution it has the constant rank

Upvotes: 0

LogicStuff
LogicStuff

Reputation: 19607

You need to make the constructor less greedy. This can be done via SFINAE:

template <typename T>
using remove_const_from_pointer_t =
  std::conditional_t<std::is_pointer<T>::value,
    std::add_pointer_t<std::remove_const_t<std::remove_pointer_t<T>>>, T>;

template <typename T>
class Wrapper {
  T val;

  template <typename U>
  friend class Wrapper;

public:
  Wrapper() = default;

  template <
    typename U,
    std::enable_if_t<
      std::is_same<U, remove_const_from_pointer_t<T>>::value, int*> = nullptr>
  Wrapper(Wrapper<U> x) : val(x.val) {}
};

You might want to try this instead of my remove_const_from_pointer_t.

Also notice that I had to add a friend declaration.

Edit: this does not work in case of just one void foo(Wrapper<bool>) overload, you'd have to move the application of SFINAE from the Wrapper's constructor directly to this overload:

template <
  typename T, 
  std::enable_if_t<
    std::is_same<std::remove_const_t<T>, char>::value, int*> = nullptr>
void foo(Wrapper<T *>) { }

Upvotes: 1

Pavan Chandaka
Pavan Chandaka

Reputation: 12751

You are missing const in front of char* in the main.

Declare as said below. It should work.

Wrapper<const char *> cp;

Below is the test and the results

http://rextester.com/FNOEL65280

Upvotes: 0

Related Questions