C++ Logo

std-proposals

Advanced search

P0466: about the danger of type parameters deduced by &C::m

From: Kazutoshi Satoda <k_satoda_at_[hidden]>
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.

-- 
k_satoda

Received on 2020-12-04 22:37:26