1

I am having a problem with the changes that were made to the way C++ templates are compiled, between the C++17 and 19 standards. Code that used to compile in VS2017 throws a compiler error since I upgraded to VS2019 or VS2022.

Situations have to do with the fact that the compiler now runs a basic syntax check on the template definition when it sees this definition ("first pass") and not only when the template is actually used.

Code example 1:

class Finder
{
    template<typename T>
    T convert_to(HANDLE h)
    {
        return Converters::Converter<T>::Convert(get_data(h));
    }
};

Here, the template class Converter<> resides in namespace Converters, and get_data is a member function of Finder which returns something that can be passed into the Convert function.

Since we're dealing with templates, this code sits in a header file "Finder.h". The header file doesn't #include "Converters.h". Finder.h is shared across several projects, some of which don't even know the Converters.h file namespace.

As long as no code calls the MyClass::convert_to<> function, this compiles in VS2017, but not so in VS2019 and VS2022:

error C3861: 'Converters': identifier not found

The obvious solution is, of course, to #include "Converters.h" either in this header file, or in the precompiled headers file. However, as was said, Converters.h is not known in all places which use MyClass. Another solution would be to use archaic #define CONVERTERS_H in the Converters.h header and enclose the function definition in #ifdef CONVERTERS_H, but this looks really ugly.

My question is: Is there a way to prevent the compiler from doing this "first pass"? Or to re-write this code so that it compiles? I don't mind if it's MS specific; no other compiler will ever see the code.

Code example 2:

class MyClass2
{
    template<class T>
    static void DoSomething(T* ptr) { static_assert(false, "Don't do this"); }
    // lots more member functions, most of them 'static'
};
template<> void MyClass::DoSomething(CWnd* ptr) { /*some useful code*/ }
/// and some more specializations of DoSomething

The intention is that the static_assert should emit an error message whenever DoSomething is called with an argument for which no explicit specialization of this template function is defined. This worked in VS2017, but in VS2022, the "first pass" of the compiler triggers the static_assert.

Again, I wonder how I could achieve this effect, other than by replacing the static_assert by a run-time assertion.

Or am I thinking into a completely wrong direction?

Thanks

Hans

h.s.
  • 139
  • 9
  • Presuming that `Converters` is a namespace, did you try adding a forward declaration of the referenced template? – Sam Varshavchik Jan 23 '23 at 12:22
  • 2
    there is no C++19, I think you are confusing versions of the standard with versions of your IDE .... or compiler? – 463035818_is_not_an_ai Jan 23 '23 at 12:22
  • 4
    The first pass is mandatory. MS handled template incorrectly for ages, until pushing for more conformance in recent years. Permissive mode is no longer enabled by default in newer releases of VS. But you can still enable it yourself (though I'd advise against it; include what you use). – StoryTeller - Unslander Monica Jan 23 '23 at 12:23
  • If the types in `Converters` are difficult to forward declare, you can take a leaf out of what the standard library does. There is [`iosfwd`](https://stackoverflow.com/questions/4300696/what-is-the-iosfwd-header) that forward declares all the hefty templates and aliases in the standard stream headers. It's a lightweight header that one can use to define lean interfaces. – StoryTeller - Unslander Monica Jan 23 '23 at 12:26

2 Answers2

0

(partial answer)

To fix MyClass2, the usual trick is to make false depend on T, so that the first pass does not trigger the assert.

// dependent false
template <typename>
constexpr bool dep_false() { return false; }

class MyClass2
{
    template<class T>
    static void DoSomething(T* ptr) {
       static_assert(dep_false<T>(), "Don't do this");
    }
    // lots more member functions, most of them 'static'
};

// specialization example
template<>
void MyClass2::DoSomething<int>(int* ptr) {
    std::cout << "int* is OK\n";
}
chi
  • 111,837
  • 3
  • 133
  • 218
0

The first case requires a forward declaration of some kind, that's unavoidable.

The second case, though, can be handled with just a minor change.

#include <type_traits>

class CWnd {};

class MyClass2
{
public:
    template<class T, class Y=T>
    static void DoSomething(T* ptr) { static_assert(!std::is_same_v<Y,T>, "Don't do this"); }
};

template<> void MyClass2::DoSomething(CWnd* ptr) { /*some useful code*/ }

void foo()
{
    int a;
    CWnd b;

    MyClass2::DoSomething(&a); // ERROR
    MyClass2::DoSomething(&b); // OK
}
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148