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;
}
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With