skip to Main Content

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


  1. 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

    Login or Signup to reply.
  2. 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:

    // Primary template declaration
    template<typename INT, typename std::enable_if<std::is_integral<INT>::value>::type* = nullptr>
    inline INT Test(size_t x);
    
    // Explicit specialization declaration
    template<> uint64_t Test<uint64_t>(size_t x);
    
    // Primary template definition
    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));
    }
    
    // Explicit specialization definition
    template<> uint64_t Test<uint64_t>(size_t x)
    {
        return 0;
    }
    

    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, then inline must be present on the explicit specialization declaration.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search