Search code examples
c++templatesvisual-c++compiler-bug

C++ template resolution error in recent MSVC, OK with GCC, LLVM and older MSVC


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?


Solution

  • 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:

    // declaration for primary template
    template<typename INT, typename std::enable_if<std::is_integral<INT>::value>::type* = nullptr>
    inline INT Test(size_t x);
    
    template<typename INT, typename std::enable_if<std::is_integral<INT>::value>::type*>
    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;
    }
    

    works now