0

Consider the following toy code:

#include <boost/hana/transform.hpp>
#include <range/v3/view/transform.hpp>

auto constexpr f = [](auto) {
    using namespace ranges::views;
    auto xxx = transform;
};

void caller() {
    using boost::hana::transform;
    f(1);
}

It compiles fine with GCC and MS' compiler, which means that using boost::hana::transform; is not affecting the names available in f's body, so it's unambiguous that xxx is ranges::views::transform.

On the other hand, if I change using boost::hana::transform; to using namespace boost::hana;, then Visual Studio claims that transform in f's body is an ambiguous name.

Is this a bug in GCC or Visual Studio? Is it a known bug? What is it due to?

Here's a small example (run it):

#include <boost/hana/transform.hpp>
#include <range/v3/view/transform.hpp>

auto constexpr f = [](auto) {
    using namespace ranges::views;
    auto xxx = transform;
};

void caller() {
#if 1
    using namespace boost::hana;
#else
    using boost::hana::transform;
#endif
    f(1);
}
Enlico
  • 23,259
  • 6
  • 48
  • 102

1 Answers1

1

It is an MSVC bug that has to do with generic lambdas. A minimal example is

void foo() {}

namespace B {
   void foo() {}
}

auto moo = [](auto) {
   foo();
};

int main() {
   using namespace B;
   moo(1);
}

It reproduces with c++17 and c++20 settings.

MSVC is known to have non-conforming handling of template instantiation and two-phase name lookup. Many of these bugs are fixed in the latest versions of the compiler, but apparently not all. Here, after moo is instantiated, the name foo is apparently looked up in the instantiation context. This should not happen because it is not a dependent name.


A related example:

#include <iostream>

namespace A{
   template <typename K>
   int foo(K) { return 1; }
}

using namespace A;
namespace B {
   struct BB {};
}

auto moo = [](auto i) {
   return foo(i);
};

void test1() {
   std::cout << moo(B::BB{}); // Comment this and see how the output changes
}

namespace B {
   int foo(BB) { return 2; }
}

void test2() {
   std::cout << moo(B::BB{});
}

int main() {
    test1();
    test2();
}

(See it here in action.)

Enlico
  • 23,259
  • 6
  • 48
  • 102
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • Can you give an example of when looking up in the instantiation context is correct? In this case, I think passing `moo`'s parameter to `foo` (both `foo`s declared as `foo(int)`, for instance) would make `foo` a dependent name; but in this case, how would the call be ambiguous? – Enlico Sep 18 '21 at 06:23
  • I don't know if there exist a context that can introduce this exact ambiguity. "For a function call where the postfix-expression is a dependent name, the candidate functions are found using the usual lookup rules from the template definition context" (temp.dep.candidate) (note, not from the instantiation context). There could be similar ambiguities for non-functions but I didn't look for them. – n. m. could be an AI Sep 18 '21 at 07:14
  • When doing ADL though, the lookup is done in both the definition context and the instantiation context, see [this example](https://godbolt.org/z/1Kh3oEfdd) (comment out the marked line and see what happens). – n. m. could be an AI Sep 18 '21 at 07:32
  • What is that!? Do I understand correctly?: when compiling `test1`, the non-commented line triggers the instantiation of `moo` with `auto == B::BB`, but `B::foo` has not been seen yet, so `A::foo` is instantiated with `K == B::BB`; when compiling `test2`, the same `moo` instantiation that has just been compiled and that calls `A::foo` matches, so it's picked up again; this way `B::foo` is not called. If the line is commented, instead, `moo` is instantiated when both `A::foo` and `B::foo` are visible, but the latter is preferred because it's not a template. Is this the full story? – Enlico Sep 18 '21 at 08:17
  • Yes that's what I understand too. – n. m. could be an AI Sep 18 '21 at 08:18