Consider declaring an unconstrained ns::operator*
. After using namespace ns
in block scope and calling a function foo<T>
, clang uses ns::operator*
when reading the iterators of a range-based loop inside foo
. There are no other types from ns
involved, so ADL should result in no candidates.
In the following example, static_assert
fails with the message:
error: static assertion failed due to requirement 'std::is_same_v<const int &, const custom_type &>'
The assembly code shows that ns::operator*
is used by clang. The assertion passes for gcc and msvc!
namespace ns {
template <typename T>
constexpr auto operator*(T&& /*value*/) {
struct custom_type {};
return custom_type{};
};
} // namespace ns
template <typename T>
constexpr void foo() {
std::vector<T> vec{};
for (const auto& curr : vec) {
static_assert(std::is_same_v<const T&, decltype(curr)>);
}
}
int main() {
using namespace ns;
foo<int>();
}
Here's a godbolt link: https://godbolt.org/z/z5vf48Mda.
I'm not able to reproduce the problem by invoking operator*
inside foo
manually. Additionally, the assertion passes for clang when foo
is not templated or not constexpr
. It's as if clang inlines foo<T>
and therefore treats ns::operator*
as a valid candidate. I'm almost sure that clang is broken, because the problem only occures when using range-based loops.
Is either clang or gcc/msvc correct, or are we dealing with unspecified/undefined behaviour?
There seems to be a bug in Clang. The using namespace ns
in main
shouldn't have any effect on the lookup in foo
and it should be impossible to find ns::operator*
in foo
by any form of lookup.
Your observations suggest to me that there is a bug in how lookup is handled in range-for loops (which have some special lookup rules).
Inlining functions is a compiler optimization under the as-if rule. It never has impact on the behavior or well-formedness of a program. It shouldn't affect lookup in any way.