Date: Sat, 5 Dec 2020 13:37:21 +0900
I saw P0466R5 was adopted, and wonder if it can be safer.
from https://timsong-cpp.github.io/cppwp/n4861/meta.member
> template<class S, class M>
> constexpr bool is_pointer_interconvertible_with_class(M S::*m) noexcept;
>
> Mandates: S is a complete type.
> Returns: true if and only if S is a standard-layout type, M is an
> object type, m is not null, and each object s of type S is
> pointer-interconvertible ([basic.compound]) with its subobject s.*m.
...
> [ Note: The type of a pointer-to-member expression &C::b is not always a
> pointer to member of C, leading to potentially surprising results when
> using these functions in conjunction with inheritance. [ Example:
>
> struct A { int a; }; // a standard-layout class
> struct B { int b; }; // a standard-layout class
> struct C: public A, public B { }; // not a standard-layout class
>
> static_assert( is_pointer_interconvertible_with_class( &C::b ) );
> // Succeeds because, despite its appearance, &C::b has type
> // “pointer to member of B of type int”.
> static_assert( is_pointer_interconvertible_with_class<C>( &C::b ) );
> // Forces the use of class C, and fails.
...
So, calling this function without specifying the class to test against
is dangerous. I understand it looks redundant if the class doesn't have
base classes, but if we put
static_assert(std::is_pointer_interconvertible_with_class(&C::b)) to
prevent unwanted future changes, it can miss to catch such a change
involving base classes. (For example, extracting some existing members
to a new common base classes.)
Can't we have another non-deducible type parameter to force specifying
the class to test against?
How about to change like the following:
template<class S, class X, class M>
constexpr bool is_pointer_interconvertible_with_class(M X::*m) noexcept;
Mandates: S and X are complete types, and is_base_of_v<X, S> is true.
...
[ Note: The type of a pointer-to-member expression &C::b is not always a
pointer to member of C. So the template argument for X deduced by the
argument &C::b can be a base class of C and it would give different
result if it were used in place of S. [ Example:
struct A { int a; }; // a standard-layout class
struct B { int b; }; // a standard-layout class
struct C: public A, public B { }; // not a standard-layout class
static_assert( is_pointer_interconvertible_with_class<C>( &C::b ) );
// Fails because C is not a standard-layout class.
// The template argument for parameter X is B because, despite
// its appearance, &C::b has type“pointer to member of B of type int”.
static_assert( is_pointer_interconvertible_with_class<B>( &C::b ) );
// Succeeds. This would happen if S were deduced by &C::b.
from https://timsong-cpp.github.io/cppwp/n4861/meta.member
> template<class S, class M>
> constexpr bool is_pointer_interconvertible_with_class(M S::*m) noexcept;
>
> Mandates: S is a complete type.
> Returns: true if and only if S is a standard-layout type, M is an
> object type, m is not null, and each object s of type S is
> pointer-interconvertible ([basic.compound]) with its subobject s.*m.
...
> [ Note: The type of a pointer-to-member expression &C::b is not always a
> pointer to member of C, leading to potentially surprising results when
> using these functions in conjunction with inheritance. [ Example:
>
> struct A { int a; }; // a standard-layout class
> struct B { int b; }; // a standard-layout class
> struct C: public A, public B { }; // not a standard-layout class
>
> static_assert( is_pointer_interconvertible_with_class( &C::b ) );
> // Succeeds because, despite its appearance, &C::b has type
> // “pointer to member of B of type int”.
> static_assert( is_pointer_interconvertible_with_class<C>( &C::b ) );
> // Forces the use of class C, and fails.
...
So, calling this function without specifying the class to test against
is dangerous. I understand it looks redundant if the class doesn't have
base classes, but if we put
static_assert(std::is_pointer_interconvertible_with_class(&C::b)) to
prevent unwanted future changes, it can miss to catch such a change
involving base classes. (For example, extracting some existing members
to a new common base classes.)
Can't we have another non-deducible type parameter to force specifying
the class to test against?
How about to change like the following:
template<class S, class X, class M>
constexpr bool is_pointer_interconvertible_with_class(M X::*m) noexcept;
Mandates: S and X are complete types, and is_base_of_v<X, S> is true.
...
[ Note: The type of a pointer-to-member expression &C::b is not always a
pointer to member of C. So the template argument for X deduced by the
argument &C::b can be a base class of C and it would give different
result if it were used in place of S. [ Example:
struct A { int a; }; // a standard-layout class
struct B { int b; }; // a standard-layout class
struct C: public A, public B { }; // not a standard-layout class
static_assert( is_pointer_interconvertible_with_class<C>( &C::b ) );
// Fails because C is not a standard-layout class.
// The template argument for parameter X is B because, despite
// its appearance, &C::b has type“pointer to member of B of type int”.
static_assert( is_pointer_interconvertible_with_class<B>( &C::b ) );
// Succeeds. This would happen if S were deduced by &C::b.
-- k_satoda
Received on 2020-12-04 22:37:26