C++ Logo

std-discussion

Advanced search

Re: Ambiguous specification of INVOKE?

From: Eric Schmidt <eric41293_at_[hidden]>
Date: Sun, 30 Oct 2022 12:01:11 -0700
Some possible resolutions are:

1. Prefer the first (non reference_wrapper) case.
2. Prefer the second case.
3. The choice is unspecified.
4. is_invocable is false. std::invoke is ill-formed.

I think option 1 is best. This is because if the condition for the first
alternative holds, the second alternative will almost certainly result
in an ill-formed expression.

I mean, it's possible to create a situation where the second is
well-formed, but I had to go through some contortions to come up with an
example:

struct S{};

template<>
class std::reference_wrapper<S>
{
     Insert implementation according to the standard

public:

     int x;
};

using refwrap = std::reference_wrapper<S>;

template<>
class std::reference_wrapper<refwrap> : private refwrap
{
     static S s;

     Insert implementation according to the standard
     Constructors initialize refwrap base with s
};

template<class T>
constexpr bool is_reference_wrapper = false;

template<class T>
constexpr bool is_reference_wrapper<std::reference_wrapper<T>> = true;

void foo(std::reference_wrapper<refwrap> t1)
{
     using T = refwrap;
     auto f = &T::x;

     // First case condition holds
     static_assert(
         std::is_base_of_v<T, std::remove_reference_t<decltype(t1)>>);

     // Ill-formed because refwrap is a private base
     // Changing it to public makes this well-formed
     t1.*f;

     // Second case condition holds
     static_assert(is_reference_wrapper<T>);

     // Well-formed
     t1.get().*f;
}

On 10/30/22 2:10 AM, Lénárd Szolnoki via Std-Discussion wrote:
> Hi,
>
> That indeed looks ambiguous to me. I expected the typical "if A is well
> formed then do A else if ..." chain in the standard wording, but it's
> not used here.
>
> Cheers,
> Lénárd
>
>
> On 30 October 2022 03:29:41 CET, Eric Schmidt via Std-Discussion
> <std-discussion_at_[hidden]> wrote:
>
> In [func.require], we have the definition of INVOKE(f, t1, t2, ..., tN), which has a number of cases. The first two are
>
> (t1.*f)(t2, ..., tN) when f is a pointer to a member function of a class T and is_­base_­of_­v<T, remove_­reference_­t<decltype(t1)>> is true;
>
> and
>
> (t1.get().*f)(t2, ..., tN) when f is a pointer to a member function of a class T and remove_­cvref_­t<decltype(t1)> is a specialization of reference_­wrapper;
>
> There is no mention of which of these is to be preferred if the conditions hold in both cases.
>
> So, given
>
> using refwrap = std::reference_wrapper<int>;
>
> is std::is_invocable_v<void (refwrap::*)(), refwrap> true? If the first case takes precedence, then yes. If the second case, then no.
>
> A quick check on Godbolt shows that GCC and Clang evaluate the previous expression to false, while MSVC evaluates it to true.
>
> A similar ambiguity exists for the pointer-to-data-member case.
>
> (I had originally written the following example, which yields a compiler error on GCC and Clang, but is accepted by MSVC. But then I realized that it violates the general ban on creating pointers to library functions.)
>
> void f(std::reference_wrapper<int> x)
> {
> std::invoke(&std::reference_wrapper<int>::get, x);
> }
> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
> <https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion>
>
>

Received on 2022-10-30 19:01:44