Search code examples
c++compiler-errorsoverloadingoverload-resolution

How to make a wrapper<int> class with a converting constructor win against wrapper<double> in overload resolution?


I'm trying to design a wrapper to tackle arithmetic params with some special logic.

https://godbolt.org/z/zG959e5Pz

#include <stdio.h>

template <class T>
struct wrapper {
    wrapper(const T& o): value(o) {}
    T value;
};

void func(wrapper<int> a) {
    printf("int: %d\n", a.value);
}


int main () {
    func(1); // int: 1
    func(1.0); // int: 1
}

However, when I declare an overload function for func, I meet a compile error. It seem that the compiler cannot choose a concrete overloaded func.

#include <stdio.h>

template <class T>
struct wrapper {
    wrapper(const T& o): value(o) {}
    T value;
};

void func(wrapper<int> a) {
    printf("int: %d\n", a.value);
}

void func(wrapper<double> a) {
    printf("double: %.2f\n", a.value);
}

int main () {
    func(1);
    // func(1.1);
}

https://godbolt.org/z/YEo4c3E69

the compiler error

<source>: In function 'int main()':
<source>:18:9: error: call of overloaded 'func(int)' is ambiguous
   18 |     func(1);
      |     ~~~~^~~
<source>:9:6: note: candidate: 'void func(wrapper<int>)'
    9 | void func(wrapper<int> a) {
      |      ^~~~
<source>:13:6: note: candidate: 'void func(wrapper<double>)'
   13 | void func(wrapper<double> a) {
      |      ^~~~

How can I fix it?


Solution

  • Make the constructor a template, accepting any type. Then reject types other than T with SFINAE. This will prevent implicit argument conversions when calling it.

    #include <concepts>
    
    template <class T>
    struct wrapper {
        template <std::same_as<T> U = T>
        wrapper(const U &o): value(o) {}
        T value;
    };
    

    Or pre-C++20:

    template <class T>
    struct wrapper {
        template <typename U = T, std::enable_if_t<std::is_same_v<T, U>, std::nullptr_t> = nullptr>
        wrapper(const U &o): value(o) {}
        T value;
    };