Here's a simple example:
template<typename T>
struct t1 : protected T
{
};
template<typename T>
struct t2
{
template<auto Index>
struct inner : t1<T>
{
private:
template<auto I>
requires(Index == I)
[[nodiscard]] friend constexpr const T& get(const inner& t) noexcept
{
return t;
}
};
};
template<typename T, typename U>
struct t3 : t2<T>::template inner<0>, t2<U>::template inner<1>
{
};
void foo()
{
const t3<false_type, tuple<int, float>> v{};
auto v1 = get<0>(v);
auto v2 = get<1>(v);
}
The type inner has a hidden friend get, but std::tuple also has std::get in global function, which emits the following errors.
Clang output:
<source>:34:15: error: call to 'get' is ambiguous
auto v1 = get<0>(v);
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/13.0.1/../../../../include/c++/13.0.1/tuple:1795:5: note: candidate function [with __i = 0, _Elements = <int, float>] get(const tuple<_Elements...>& __t) noexcept
<source>:19:49: note: candidate function [with I = 0]
[[nodiscard]] friend constexpr const T& get(const inner& t) noexcept
GCC gives the similar errors:
<source>: In function 'void foo()':
<source>:34:21: error: call of overloaded 'get<0>(const t3<std::integral_constant<bool, false>, std::tuple<int, float> >&)' is ambiguous
auto v1 = get<0>(v);
In file included from :1:
/opt/compiler-explorer/gcc-trunk-20230312/include/c++/13.0.1/tuple:1795:5: note: candidate:
'constexpr std::__tuple_element_t<__i, std::tuple<_UTypes ...> >& std::get(const tuple<_UTypes ...>&) [with long unsigned int __i = 0; _Elements = {int, float}; __tuple_element_t<__i, tuple<_UTypes ...> > = int]'
get(const tuple<_Elements...>& __t) noexcept
<source>:19:49: note: candidate: 'constexpr const T& get(const t2::inner&) [with auto I = 0; auto Index = 0; T = std::integral_constant<bool, false>]'
[[nodiscard]] friend constexpr const T& get(const inner& t) noexcept
Surprisingly, MSVC accept the code.
If we replace struct t1 : protected T with struct t1 : private T, MSVC will print:
error C2243: 'type cast': conversion from 'const t2::inner<0> *' to 'const T &' exists, but is inaccessible
The adl description in cppreference.com says:
...
Otherwise, for every argument in a function call expression its type is examined to determine the associated set of namespaces and classes that it will add to the lookup.
For arguments of fundamental type, the associated set of namespaces and classes is empty
For arguments of class type (including union), the set consists of
a) The class itself
b) All of its direct and indirect base classes
c) If the class is a member of another class, the class of which it is a member
d) The innermost enclosing namespaces of the classes added to the set
...
In conclusion, compiler instantiates the std::get for std::tuple using inner as argument without access check, am I right?
But how can my hidden friend get be invoked under such inheritance without compile errors?