2

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.

Fidget324
  • 33
  • 4

4 Answers4

1

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 *>) { }
LogicStuff
  • 19,397
  • 6
  • 54
  • 74
  • 1
    Thank you for the response, but it does seem to only address the example I have given, and possibly a few other cases similar to it. Please see my recent EDIT. – Fidget324 Aug 15 '17 at 21:05
1

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

O'Neil
  • 3,790
  • 4
  • 16
  • 30
  • I think that this is the best answer, although it's not exactly what I had been hoping for. This technique is a general solution to the overload problem I had posed... I was hoping for a solution which would not require a wrapper around the overloaded function, but I'm now convinced that this is not possible. Thanks! – Fidget324 Aug 21 '17 at 19:07
0

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

Pavan Chandaka
  • 11,671
  • 5
  • 26
  • 34
0

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