3

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.

  1. For arguments of fundamental type, the associated set of namespaces and classes is empty

  2. 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?

C0nstexpr
  • 86
  • 6
  • 1
    Yes, name lookup (including ADL) doesn't consider accessibility, which is considered even after overload resolution. – songyuanyao Mar 13 '23 at 03:53
  • 1
    For design perspective, I prefer composition over inheritance. Is there any challenge preventing you from making `std::tuple` as an member variable of `t3` instead of inheritance? – Louis Go Mar 13 '23 at 06:37
  • 1
    @LouisGo I hope the empty base optimization was kept under inheritance – C0nstexpr Mar 13 '23 at 07:16

0 Answers0