A cpp file and two headers ended up forming this "problematic" translation unit:²
namespace na {
struct Foo {
friend int getFoo(Foo const&, int const&) {
return 1;
}
};
inline constexpr auto makeFoo = [](auto const& sys) {
return [sys]{
return getFoo(sys, 0);
};
};
}
constexpr auto getFoo = [](auto const&) { return 1; };
namespace nc {
int bar() {
return na::makeFoo(na::Foo{})();
}
}
Problematic is in quotes because, whereas MSVC refuses it, GCC and Clang seem to be ok with this code, and I'd like to understand which compiler is correct.
The function getFoo(Foo const&, int const&)
can only be found via ADL, because it's a friend
declared and defined in the class Foo
; furthermore it doesn't conflict with the lambda object getFoo
because they're not in the same namespace¹.
Clang and GCC do find that friend
function and use that.
On the other hand, MSVC has a different opinion, and finds the lambda instead, thus failing to compile because of the different number of arguments.
(¹) Indeed, it's enough to move that lambda inside namespace na {
, and we get the expected redefinition of 'getFoo' as different kind of symbol
error on all three compilers.
(²) In the real case, the lambda turned out the be dead code, so deleting it fixes the problem.
Another solution would have been (and maybe it's still a good idea to do so) to make makeFoo
a non generic lambda, by changing auto
to Foo
, thus forcing the look up for getFoo
to happen at makeFoo
's definition context, when the lambda hasn't been seen yet.
Simplifying the code makes the matter a bit clearer:
inline constexpr auto makeFoo = [](auto const& sys) {
return [sys]{
return getFoo(sys);
};
};
constexpr auto getFoo = [](auto const&) { return 1; };
int bar() {
return makeFoo(32)();
}
This code mistakenly compiles on MSVC.
Indeed, ADL is completely absent, because two entities out of three, getFoo
and makeFoo
, are lambdas, and the third one, bar
, is a nullary function.
This means that getFoo
(a lambda) can't be found at the POI of makeFoo
, i.e. during the second phase of name lookup (because the second phase lookup for unqualified names is only an ADL); on the other hand, during the first phase, at template definition time, getFoo
is not visible yet.
Furthemore, if the call to getFoo
is done in the body of makeFoo
, rather than in an inner lambda,
inline constexpr auto makeFoo = [](auto const& sys) {
return getFoo(sys);
};
constexpr auto getFoo = [](auto const&) { return 1; };
int bar() {
return makeFoo(32);
}
the code then does fail to compile in MSVC too.
The conclusion is I've hit one more bug (or a different shade of a known bug) on MSVC.