C++ Template specialization by type of non-type parameter

1.1k Views Asked by At

Is it valid to have variations of the same template function that differ by the type of a non-type member?

template<typename T, unsigned int V>
void f(unsigned int& v) { v = V; }

template<typename T, bool B>
void f(bool& b) { b = B; }

The intent is that one be able to call

unsigned int meaningOfLife;
f<sometype, 42>(meaningOfLife);

bool areYouAlive;
f<sometype, true>(areYouAlive);

clang and gcc are silent but MSVC reports

warning C4305: 'specialization': truncation from 'int' to 'bool'

I'd like to avoid requiring specification of the constant type:

f<sometype, bool, true>

and want to ensure that the constant value and the destination value match.

---- mcve ----

#include <iostream>

template<unsigned int V>
void f(unsigned int& v) { v = V; }

template<bool B>
void f(bool& b) { b = B; }

int main()
{
    unsigned int u { 0 };
    bool b { false };

    f<42>(u);
    f<true>(b);

    std::cout << u << b;
}

Rextester example: http://rextester.com/VIGNP16100

Warning(s):
source_file.cpp(14): warning C4305: 'specialization': truncation from 'int' to 'bool'
/LIBPATH:C:\boost_1_60_0\stage\lib 
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.23506 for x64
421
1

There are 1 best solutions below

0
On BEST ANSWER

Short answer: The code is OK and MSVC emits a bogus warning.

MSVC and g++ both have bugs in non-type template argument matching but they do select the right one for your particular example.


Long answer: It is OK to have overloaded function templates with non-type template parameters.

However the matching of a template-argument to a template declaration does not work as might be expected (by me anyway). ALL matching templates are entered into overload resolution. It does not, at any stage, prefer an "exact match".

According to C++17 [temp.arg.nontype/]2, converted constant expressions are allowed. That means, for example:

  • 42 matches int and unsigned int.
  • 42u matches int and unsigned int.
  • 1u matches unsigned int, int and bool.

Note that a converted constant expression cannot contain a narrowing conversion, and int to bool is narrowing unless the value is a constant expression of value 0 or 1. So 42 does not match bool. (Ref: C++17 [expr.const]/4).

If we had the following setup:

template<unsigned int V> void g() {}
template<bool B> void g() {}

then the correct behaviour is:

  • g<42>() calls g<unsigned int>.
  • g<1>() is ambiguous.
  • g<1u>() is ambiguous.

MSVC 2017 and g++ 7,8 all incorrectly allow g<42> to match g<bool>, and report g<42> as ambiguous.

MSVC emits the warning that you see whilst generating the invalid match; g++ gives no diagnostic at all. If we remove the unsigned int overload then g++ silently accepts the invalid code with no diagnostic.


In your code there is a non-const lvalue reference parameter:

template<unsigned int V>  void h(unsigned int&) {}
template<bool B>          void h(bool&) {}

This makes a difference because overload resolution can make a selection based on the function argument. For the call:

unsigned int m;
h<1u>(m);

then both overloads of h are entered into overload resolution, however then h<unsigned int> wins because h<bool>(m) would be invalid.

As discussed above, The call h<42>(m); wins at the first stage because this cannot match h<bool>; but in MSVC++ (and g++) it incorrectly allows h<bool> to go through at this stage, but prunes it later as for the h<1u> case.