Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bizarre template errors when compiling in Visual C++ with /permissive-

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?

like image 513
Angus Graham Avatar asked Apr 10 '19 08:04

Angus Graham


People also ask

What is the main problem with templates C++?

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.

How will you restrict the template for a specific datatype?

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.

What is the rule of compiler by templates?

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.


2 Answers

There are a lot of C++ rules at play here.

  • Qualified name lookup
  • Unqualified name lookup
  • Argument-dependent lookup
  • Dependent name lookup and template name resolution (a.k.a. two-phase lookup)

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.

like image 66
rustyx Avatar answered Oct 21 '22 01:10

rustyx


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:

  1. Implement correct two-phase lookup for C++ templates

  2. Using /permissive- in a C++/CLI project triggers two-phase name lookup warnings

like image 22
P.W Avatar answered Oct 21 '22 02:10

P.W