Search code examples
c++templatesvariadic-templates

Function template-value overloaded by value's type


In a variadric non-type template, each value on the template list has its own type:

template <typename T> void print_type(const T &) { std::cout << typeid(T).name() << '\n'; }

template <auto ... VALUE> void print_types_0() { (print_type(VALUE), ...); }

int main()
{
    print_types_0<0u, 0ll>(); // prints "j x " (gcc 13.2.0)
}

So, I was expecting the code below to work as well:

template <unsigned U> void vtemplate()      { std::cout << "unsigned "; }

template <long long LL> void vtemplate()    { std::cout << "long long "; }

template <auto ... VALUE> void print_types_1() { (vtemplate<VALUE>(), ...); }

int main()
{
    print_types_1<1u, 1ll>(); // error!
}

But it fails due to ambiguity:

In instantiation of 'void print_types_1() [with auto ...VALUE = {1, 1}]':
  required from here
error: call of overloaded 'vtemplate<1>()' is ambiguous
template <auto ... VALUE> void print_types_1() { (vtemplate<VALUE>(), ...); }
                                                   ~~~~~~~~~~~~~~~~^~
note: candidate: 'void vtemplate() [with unsigned int U = 1]'
template <unsigned U> void vtemplate()      { std::cout << "unsigned "; }
                           ^~~~~~~~~
note: candidate: 'void vtemplate() [with long long int LL = 1]'
template <long long LL> void vtemplate()     { std::cout << "long long "; }        
                             ^~~~~~~~~

I'm not sure why it fails. My guess is that there's no such thing as "template-value overload", and even if 1u and 1ll are rightfully detected as unsigned and long long by the compiler, both values can be cast to each other's type, hence both vtemplate instances are equally valid. On the other hand, it works for print_types_0 because each value is a better match to the function argument, and so there's no ambiguity.

I'm not sure if my explanation is correct. I was expecting print_types_0 and print_types_1 to behave almost the same.

Is there a way to achieve this "value-template" overload?


Solution

  • Overload resolution starts by converting each function template in the overload set into a function or else discarding it if template argument deduction fails, but the check for better implicit conversion sequences applies only to function arguments/parameters. The closest analog for template parameters is partial ordering, but that too relies on the ordinary parameter types and so cannot disambiguate based on a template parameter that must be explicitly specified.

    You can approximate this interface by deducing the template argument from a stateless function parameter:

    template<auto> struct constant {};
    
    template <unsigned U> void vtemplate(constant<U>)    { std::cout << "unsigned "; }
    template <long long LL> void vtemplate(constant<LL>) { std::cout << "long long "; }
    
    template <auto ... VALUE> void print_types_1() { (vtemplate(constant<VALUE>()), ...); }