3

Consider the following snippet:

template <class T>
struct remove_pointer
{
};

template <class T>
struct remove_pointer<T*>
{
    typedef T type;
};

template <typename T>
T
clone(const T& v)
{
    return v;
}


template <typename T, typename U = typename remove_pointer<T>::type>
T
clone(const U& v)
{
    return new U(v);
}


int main() 
{
    auto foo = clone<double>(42.0);
    return 0;
}

This code generates compilation errors:

 In function 'int main()':
30:34: error: call of overloaded 'clone(double)' is ambiguous
30:34: note: candidates are:
14:1: note: T clone(const T&) [with T = double]
22:1: note: T clone(const U&) [with T = double; U = double]

Why is the compiler deriving T=double, U=double in line 22? I thought it only should pass if T is a pointer type.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
user3612643
  • 5,096
  • 7
  • 34
  • 55

3 Answers3

2

Why is the compiler deriving T=double, U=double in line 22? I thought it only should pass if T is a pointer type.

Because template arguments are deduced based on passed non-template arguments. You passed a double literal into an argument of type const U&, so U was deduced to be double. The default of the template argument is not used if the argument is deduced.

And the problem is that both the single template argument, as well as the two template argument overloads can be called with the same argument list, so the compiler doesn't know which overload you intended to call.

eerorika
  • 232,697
  • 12
  • 197
  • 326
2

Compiler deduced U=double and didn't use your default argument at all. I think that you can try to have two different overloads for your clone function based on this answer.

Your example might look like this:

#include <iostream>
#include <type_traits>

template <typename T>
T
clone(const T& v, typename std::enable_if<!std::is_pointer<T>::value >::type* = 0)
{
    std::cout << "non ptr clone" << std::endl;
    return v;
}

template <typename T>
T
clone(const T v, typename std::enable_if<std::is_pointer<T>::value >::type* = 0)
{
    std::cout << "ptr clone" << std::endl;
    using noptrT = typename std::remove_reference<decltype(*v)>::type;
    return new noptrT(*v);
}

int main()
{
    auto foo = clone<double>(42.0);
    std::cout << foo << std::endl;
    double* ptr = new double(69.69);
    auto bar = clone<double*>(ptr);
    std::cout << *bar << std::endl;
    delete ptr;
    delete bar;
    return 0;
}

Note that this solution also allows you to pass a function pointer

void fun() {}
...
clone<void(*)()>(fun);

which will result in compilation error (new cannot be applied to function type).

pptaszni
  • 5,591
  • 5
  • 27
  • 43
1
clone<double>(42.0);

could either mean

  • "instantiate the first clone with the explicit template argument double", or

  • "instantiate the second clone with the first argument explicitly given as double and the second one deduced from 42.0 being passed in place of const U& v". The default value you provide is ignored, U is deduced from matching double to const U&.

The compiler can't decide which one you meant, so you get an error. Maybe you intended to use SFINAE to specialize the function template. But specializing function templates is strongly discouraged (http://www.gotw.ca/publications/mill17.htm).

One way to express what you want would be if constexpr: https://godbolt.org/z/_YGXGc

But the semantics of this function are still quite weird. Sometimes the function returns a heap-allocated pointer and sometimes it returns a value. The user has to pass the target type (in both cases or just one?). This will be really confusing at the call site.

Max Langhof
  • 23,383
  • 5
  • 39
  • 72