9

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?

Angus Graham
  • 133
  • 6
  • This looks like a compiler bug. The third case should fail. The rules are arcane, but you can start by looking up ADL and two phase lookup. – Passer By Apr 10 '19 at 08:49

2 Answers2

6

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.

rustyx
  • 80,671
  • 25
  • 200
  • 267
1

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

P.W
  • 26,289
  • 6
  • 39
  • 76
  • See [this post](https://stackoverflow.com/questions/6273176/what-exactly-is-broken-with-microsoft-visual-cs-two-phase-template-instanti) and its answers which details some issues with MSVC in this regard. – P.W Apr 10 '19 at 09:35
  • P.W: Both of those bug reports are closed as fixed. They claim that 2 phase lookup is implemented correctly now. The other post appears to be talking about MSVC's behavior from before it supported 2 phase lookup. – Angus Graham Apr 10 '19 at 15:01
  • @AngusGraham: If their claim is correct, should we not seeing the same compiler behavior with and without the `/Zc:twoPhase-` option? – P.W Apr 10 '19 at 17:44