C++ Logo

std-discussion

Advanced search

Re: Ambiguous specification of INVOKE?

From: Edward Catmur <ecatmur_at_[hidden]>
Date: Mon, 31 Oct 2022 09:58:40 +0000
On Sun, 30 Oct 2022 at 22:46, Eric Schmidt via Std-Discussion <
std-discussion_at_[hidden]> wrote:

> On 10/30/22 1:51 PM, Lénárd Szolnoki via Std-Discussion wrote:
> > Hi,
> >
> > On 30 October 2022 20:01:11 CET, Eric Schmidt via Std-Discussion <
> std-discussion_at_[hidden]> wrote:
> >> 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 agree that option 1 is the best. Anything that narrows down the
> special casing of reference_wrapper is a win in my book.
>
> Okay. I'll wait a couple of days, and if no one objects, submit a Defect
> Report with option 1 as the proposed resolution.
>
> (By the way, I don't understand why there is a special case here for
> reference_wrapper at all.)
>

Per https://cplusplus.github.io/LWG/issue2219 , it's to permit code like

struct X { int i; } x;
auto f = &X::i;
auto t1 = std::ref(x);
int i = std::mem_fn(f)(t1);

Discussion considered that the user cannot take the address of standard
library functions ([namespace.std]/6, then [member.functions]), but not
(apparently) [namespace.std]/2.

>> 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:
>

How about this (https://godbolt.org/z/Md3WPv1e1 ):

struct S { int i; };
struct T : S {};
template<> struct std::reference_wrapper<T> : S {
    constexpr T& get() const noexcept;
    constexpr operator T&() const noexcept;
};
int f(std::reference_wrapper<T>& t) { return std::invoke(&S::i, t); }

MSVC uses interpretation #1, libstdc++ interpretation #2, libc++ sees it as
ambiguous.

I agree that option 1 is preferable (and as Lénárd says, the wording would
be natural) but implementers may wish to comment, seeing as libstd++ and
libc++ would need to change, and perhaps there's a reason for their current
behavior.

>>
> >> 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
> >> };
> >
> > I don't think that you are allowed to specialise like this. Your
> specialisation must satisfy the requirements of the non-specialised type,
> but there is no allowance to derive from an other, unspecified standard
> library type, even privately.
>
> I thought it was allowed, based on
>
> [derivation]/1: An implementation may derive any class in the C++
> standard library from a class with a name reserved to the implementation.
>
> [res.on.macro.definitions]/1: The names and global function signatures
> described in [contents] are reserved to the implementation.
>
> [contents]/1: The C++ standard library provides definitions for the
> entities and macros described in the synopses of the C++ standard
> library headers ([headers]), unless otherwise specified.
>
> I guess [res.on.macro.definitions] means merely that a program can't
> #define reference_wrapper?
>
> In any case, my point was how hard it is to come up with an example,
> which is only strengthened if the one I gave isn't correct.
>
> >> 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>
> >>>
> >>>
> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>

Received on 2022-10-31 09:58:52