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 nsinmainshouldn't have any effect on the lookup infooand it should be impossible to findns::operator*infooby 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.