3655. The INVOKE operation and union types

Section: 22.10.4 [func.require] Status: C++23 Submitter: Jiang An Opened: 2021-12-29 Last modified: 2023-11-22

Priority: 3

View all other issues in [func.require].

View all issues with C++23 status.

Discussion:

There are two cases of the INVOKE operation specified with std::is_base_of_v (22.10.4 [func.require] (1.1), (1,4)), which means the following code snippet is ill-formed, as std::is_base_of_v<B, D> is false when either B or D is a union type.

union Foo { int x; };
static_assert(std::is_invocable_v<int Foo::*, Foo&>);

Currently libstdc++ accepts this code, because it uses slightly different conditions that handle union types. libc++ and MSVC STL reject this code as specified in 22.10.4 [func.require].

Should we change the conditions in 22.10.4 [func.require] (1.1) and (1.4) to match libstdc++ and correctly handle union types?

[2022-01-30; Reflector poll]

Set priority to 3 after reflector poll.

[2023-02-07; Jonathan adds wording]

This is a regression introduced by LWG 2219. In C++14 std::result_of<int Foo::*(Foo&)>::type was valid, because the INVOKE wording used to say "f is a pointer to member data of a class T and t1 is an object of type T or a reference to an object of type T or a reference to an object of a type derived from T". Since LWG 2219 we use is_base_of which is always false for union types. I don't think LWG 2219 intended to break this case, so we should fix it.

Previous resolution [SUPERSEDED]:

This wording is relative to N4928.

  1. Modify 22.10.4 [func.require] as indicated:

    -1- Define INVOKE(f, t1, t2, …, tN) as follows:

    • (1.1) — (t1.*f)(t2, …, tN) when f is a pointer to a member function of a class T and is_same_v<T, remove_cvref_t<decltype(t1)>> || is_base_of_v<T, remove_reference_t<decltype(t1)>> is true;
    • (1.2) — (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;
    • (1.3) — ((*t1).*f)(t2, …, tN) when f is a pointer to a member function of a class T and t1 does not satisfy the previous two items;
    • (1.4) — t1.*f when N == 1 and f is a pointer to data member of a class T and is_same_v<T, remove_cvref_t<decltype(t1)>> || is_base_of_v<T, remove_reference_t<decltype(t1)>> is true;
    • (1.5) — t1.get().*f when N == 1 and f is a pointer to data member of a class T and remove_cvref_t<decltype(t1)> is a specialization of reference_wrapper;
    • (1.6) — (*t1).*f when N == 1 and f is a pointer to data member of a class T and t1 does not satisfy the previous two items;
    • (1.7) — f(t1, t2, …, tN) in all other cases.

[2023-02-07; Jonathan provides wording change requested by LWG]

Change remove_reference_t to remove_cvref_t. is_base_of ignores cv-qualifiers, so this isn't necessary, but just using the same transformation in both cases seems simpler to grok.

[Issaquah 2023-02-07; LWG]

Move to Immediate for C++23

[2023-02-13 Approved at February 2023 meeting in Issaquah. Status changed: Immediate → WP.]

Proposed resolution:

This wording is relative to N4928.

  1. Modify 22.10.4 [func.require] as indicated:

    -1- Define INVOKE(f, t1, t2, …, tN) as follows:

    • (1.1) — (t1.*f)(t2, …, tN) when f is a pointer to a member function of a class T and is_same_v<T, remove_cvref_t<decltype(t1)>> || is_base_of_v<T, remove_referencecvref_t<decltype(t1)>> is true;
    • (1.2) — (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;
    • (1.3) — ((*t1).*f)(t2, …, tN) when f is a pointer to a member function of a class T and t1 does not satisfy the previous two items;
    • (1.4) — t1.*f when N == 1 and f is a pointer to data member of a class T and is_same_v<T, remove_cvref_t<decltype(t1)>> || is_base_of_v<T, remove_referencecvref_t<decltype(t1)>> is true;
    • (1.5) — t1.get().*f when N == 1 and f is a pointer to data member of a class T and remove_cvref_t<decltype(t1)> is a specialization of reference_wrapper;
    • (1.6) — (*t1).*f when N == 1 and f is a pointer to data member of a class T and t1 does not satisfy the previous two items;
    • (1.7) — f(t1, t2, …, tN) in all other cases.