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)
, whereget
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?
If a search for the name
get
in the scope ofE
finds at least one declaration that is a function template whose first template parameter is a non-type parameter, the initializer ise.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.