Here is some C++ template function which, in the general case, calls one of its specializations.
After recently upgrading Microsoft Visual Studio to 17.5.4 (MSVC 19.35.32217), compiling the source code below produces the following compilation errors:
bugtemplate.cpp(8,43): Error C2672: 'Test': no matching overloaded function found
Bugtemplate.cpp(6,12): message : could be 'INT Test(size_t)'
bugtemplate.cpp(9,1): message : 'INT Test(size_t)': could not deduce template argument for '__formal'
bugtemplate.cpp(8,43): message : 'Test': function declaration must be available as none of the arguments depend on a template parameter
The same code compiles with all recent versions of GCC and LLVM. It also compiles with MSVC 19.34.31944 (current compiler in GitHub CI/CD). This code is adapted from a much larger project which compiled for years on Linux, Windows, macOS and *BSD.
Source code:
#include <iostream>
#include <cstdint>
// General template
template<typename INT, typename std::enable_if<std::is_integral<INT>::value>::type* = nullptr>
inline INT Test(size_t x)
{
return static_cast<INT>(Test<uint64_t>(x));
}
// Template specialization.
template<> uint64_t Test<uint64_t>(size_t x)
{
return 0;
}
int main()
{
std::cout << Test<int>(0) << std::endl;
}
To all C++ gurus, is this a new bug in the most recent MSVC or is this invalid C++ code which went unnoticed so far with all other compilers?
2
Answers
This seems to be a msvc bug as it accepts the code in C++17 but reject it with C++20. Demo.
Note also that msvc starts accepting the code if you use a separate declaration and definition for the function template as shown at the end of the answer.
Here is the bug:
MSVC rejects valid code with c++20
Solution
One workaround that works with msvc is to have a separate declaration as shown below:
works now
I think this is IFNDR (ill-formed, no diagnostic required), meaning that the compiler does have to diagnose that the program is ill-formed.
Test<uint64_t>(x)
is non-dependent and therefore may be bound by the compiler at the point of definition of the enclosing template.Specifically, when considering a hypothetical instantiation of the enclosing function template immediately after its definition, then
Test<uint64_t>(x)
would be IFNDR, because it odr-uses a specialization of the function template that is explicitly specialized but for which the explicit specialization hasn’t been declared yet at that point (which always makes the program IFNDR, see [temp.expl.spec]/7).Based on that and because
Test<uint64_t>(x)
is a non-dependent construct, [temp.res.general]/8.4 then implies that the program itself is IFNDR.The correct approach is to declare the explicit specialization before it is used:
The explicit specialization definition may also be used directly in place of the explicit specialization declaration.
Also,
inline
on the primary template doesn’t really make any sense. However if the explicit specialization definition (rather than just the declaration) is intended to be put into a shared header, theninline
must be present on the explicit specialization declaration.