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?