I'm trying to compile some code in VS2019 with /permissive- that involves both templates and overloading and wierd things are happening. (https://godbolt.org/z/fBbQu6)
As in the godbolt, when my templateFunc() is declared between two overloads like so:
namespace Foospace {
class A;
void func(A*) {};
template<class T> void templateFunc() { Foospace::func((T*)0); }
class B;
void func(B*) {};
void func() { Foospace::templateFunc<B>(); }
}
I get error C2664: 'void Foospace::func(Foospace::A *)': cannot convert argument 1 from 'T *' to 'Foospace::A *'
If I move the templateFunc() below the overloads, it obviously works:
namespace Foospace {
class A;
void func(A*) {};
class B;
void func(B*) {};
template<class T> void templateFunc() { Foospace::func((T*)0); }
void func() { Foospace::templateFunc<B>(); }
}
And if I move templateFunc() before both overloads that also works:
namespace Foospace {
template<class T> void templateFunc() { Foospace::func((T*)0); }
class A;
void func(A*) {};
class B;
void func(B*) {};
void func() { Foospace::templateFunc<B>(); }
}
And if I keep templateFunc() between the two overloads but simply remove the Foospace
namespace qualifier from the call to func() then suddenly that also works too:
namespace Foospace {
class A;
void func(A*) {};
template<class T> void templateFunc() { func((T*)0); }
class B;
void func(B*) {};
void func() { Foospace::templateFunc<B>(); }
}
What is going on here?
It can be difficult to use/debug highly templated code. Have at least one syntactic quirk ( the >> operator can interfere with templates) Help make C++ very difficult to parse.
There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.
The compiler usually instantiates members of template classes independently of other members, so that the compiler instantiates only members that are used within the program. Methods written solely for use through a debugger will therefore not normally be instantiated.
There are a lot of C++ rules at play here.
As Herb Sutter put it, "difficulty 9/10".
Let's consider the cases one by one.
namespace Foospace {
class A;
void func(A*) {};
template<class T> void templateFunc() { Foospace::func((T*)0); }
class B;
void func(B*) {};
void func() { Foospace::templateFunc<B>(); }
}
Here we have qualified lookup inside a template. The name Foospace::func
is looked up and "bound" at the point of template definition. Only func(A*)
is known at that time and thus the subsequent call to func(B*)
fails. The compiler is right to reject the code. Note that this wasn't implemented in MSVC until recently.
namespace Foospace {
class A;
void func(A*) {};
class B;
void func(B*) {};
template<class T> void templateFunc() { Foospace::func((T*)0); }
void func() { Foospace::templateFunc<B>(); }
}
Same story, only the lookup results in the overload set func(A*)
, func(B*)
. So both calls succeed.
namespace Foospace {
template<class T> void templateFunc() { Foospace::func((T*)0); }
class A;
void func(A*) {};
class B;
void func(B*) {};
void func() { Foospace::templateFunc<B>(); }
}
Here the lookup finds nothing. The code should fail to compile. For some reason MSVC compiles it, which suggests it's either a bug or a feature/extension. GCC and clang reject it.
namespace Foospace {
class A;
void func(A*) {};
template<class T> void templateFunc() { func((T*)0); }
class B;
void func(B*) {};
void func() { Foospace::templateFunc<B>(); }
}
Unqualified lookup here. ADL rules apply, so at template definition time it's impossible to say if func(T*)
resolves or not so phase-1 lookup always lets it proceed. The name is looked up at the point of template instantiation, when both declarations are known. The code compiles fine.
There are some issues with MSVC and two-phase lookup.
In Visual Studio 2017 version 15.3 and later, by default, the compiler uses two-phase name lookup for template name resolution.
The /permissive-
option implicitly sets the conforming two-phase lookup compiler behavior, but it can be overridden by using /Zc:twoPhase-
.
If the option /Zc:twoPhase-
is specified, the compiler reverts to its previous non-conforming class template and function template name resolution and substitution behavior.
With this option set, the code compiles. See godbolt demo.
Related bug reports:
Implement correct two-phase lookup for C++ templates
Using /permissive- in a C++/CLI project triggers two-phase name lookup warnings
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With