Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

static_assert whether pointer adjustment is required for an upcast

I'd like to perform a compile-time check for whether an upcast from a derived type to a base type requires a pointer adjustment. I'm seeing nothing I could use from type_traits. Can this be done?

Here's an example of how this might be done at runtime. Note that clang even warns that the first printf would never print "yes" and the second one would never print "no", so it does evaluate those adjustment values to constants. Question is, how to access those values at compile time?

#include <stdio.h>

struct A
{
  int a;
};

struct B
{
  int b;
};

struct C: A, B
{
  int c;
};

int main()
{
  C c;

  // Prints "no"
  printf( "Is pointer adjustment required when casting from C to A? %s\n",
          ( ( char * ) ( A * ) &c - ( char * ) &c ) ? "yes" : "no" );

  // Prints "yes"
  printf( "Is pointer adjustment required when casting from C to B? %s\n",
          ( ( char * ) ( B * ) &c - ( char * ) &c ) ? "yes" : "no" );

  // Can we have those values as constexpr though?

  return 0;
}
like image 632
dragonroot Avatar asked May 16 '26 12:05

dragonroot


2 Answers

This is called pointer interconvertibility. There is a trait std::is_pointer_interconvertible_base_of which unfortunately isn't implemented yet in gcc and clang. See also the paper and issue. Pointer interconvertible types have the same address and their pointers can be casted between with reinterpret_cast.

#include <type_traits>

#ifdef __cpp_lib_is_pointer_interconvertible
    // Prints "no"
    printf( "Is pointer adjustment required when casting from C to A? %s\n",
      std::is_pointer_interconvertible_base_of_v<A, C> ? "no" : "yes" );

    // Prints "yes"
    printf( "Is pointer adjustment required when casting from C to B? %s\n",
      std::is_pointer_interconvertible_base_of_v<B, C> ? "no" : "yes" );
#endif
like image 91
Jeff Garrett Avatar answered May 18 '26 01:05

Jeff Garrett


Here's a possible implementation which doesn't require C++20:

template< class Base, class Derived >
struct is_pointer_interconvertible_base_of
{
private:
  // Note that this doesn't seem to be ODR-used in the end, so an actual
  // instance isn't needed. Clang generates a warning about needing to
  // instantiate it, but linking succeeds nevertheless
  static Derived derived;

public:
  // Suppress that clang warning :)
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundefined-var-template"
#endif
  // TODO: add std::is_base_of check etc
  static constexpr bool value = ( ( void const * ) &derived ==
                                  ( Base const * ) &derived );
#ifdef __clang__
#pragma clang diagnostic pop
#endif
};

struct A
{
  int a;
};

struct B
{
  int b;
};

struct C: A, B
{
  int c;
};

static_assert( is_pointer_interconvertible_base_of< A, C >::value,
               "pointer adjustment isn't expected when casting from C to A" );
static_assert( !is_pointer_interconvertible_base_of< B, C >::value,
               "pointer adjustment is expected when casting from C to B" );

int main()
{
  return 0;
}

Seems to work with both clang and gcc. Clang generates a warning about needing to instantiate derived, but it doesn't seem to be the case in the end. The case where Derived doesn't actually derive from Base isn't handled here, but it's easy to add an std::is_base_of check and other checks if needed. No idea of how standards-compliant this is, but it may help until C++20 support is more widely available.

like image 36
dragonroot Avatar answered May 18 '26 03:05

dragonroot