c++c++17language-lawyerstructured-bindings

Why does MSVC find ineligible get() in structured binding?


Consider the following code:

#include <utility>
#include <type_traits>
#include <cstddef>
#include <iostream>

template <typename>
struct A
{
    void get() {}    // #1
};

template <typename ...Ts>
struct B : A<Ts>... {};

template <typename ...Ts>
struct std::tuple_size<B<Ts...>> : std::integral_constant<std::size_t, 2> {};

template <std::size_t I, typename ...Ts>
struct std::tuple_element<I, B<Ts...>>
{
    using type = int;
};

template <std::size_t I, typename ...Ts>
constexpr int get(B<Ts...>)    // #2
{
    return 2;
}

int main()
{
    B<double, long long> b;
    auto [x, y] = b;
    std::cout << x << ' ' << y << std::endl;
}

GCC accepts the above code and outputs 2, 2 as I excepted (godbolt), but MSVC complains (godbolt).

error C2385: ambiguous access of 'get'

According to cppreference:

For each identifier, a variable whose type is "reference to std::tuple_element<i, E>::type" is introduced: lvalue reference if its corresponding initializer is an lvalue, rvalue reference otherwise.

The initializer for the i-th variable is

  • e.get<i>(), if lookup for the identifier get in the scope of E by class member access lookup finds at least one declaration that is a function template whose first template parameter is a non-type parameter
  • Otherwise, get<i>(e), where get is looked up by argument-dependent lookup only, ignoring non-ADL lookup.

From my understanding, the member get() (line #1) does not meet the quoted requirements (it's even not a template), and the compiler should choose the non-member get() (line #2). GCC works as I excepted, but MSVC seems stuck at the member get(), ignoring whether it is eligible.

Which compiler is correct? Why does MSVC find ineligible get() in structured binding?


Solution

  • [dcl.struct.bind]/4:

    If a search for the name get in the scope of E finds at least one declaration that is a function template whose first template parameter is a non-type parameter, the initializer is e.get<i>().

    (E is the type of the backing variable, in this case B<double, long long>.)

    If the search results in an ambiguity between members of different bases, we hit [class.member.lookup]/6, making the program ill-formed (regardless of whether the declarations found are of function templates).


    CWG2567 is closely related: it deals with a similar scenario in the context of lookup for overloaded operators.

    Under the proposed resolution for that issue, this program becomes well-formed: the search for get in B<double, long long> would fail gracefully (yielding nothing), allowing a non-member get to be used as a fallback.