C++ Logo

std-discussion

Advanced search

Re: Ambiguous specification of INVOKE?

From: Eric Schmidt <eric41293_at_[hidden]>
Date: Sun, 30 Oct 2022 15:45:44 -0700
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.)

>> 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
>> };
>
> 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>
>>>
>>>

Received on 2022-10-30 22:46:14