jwimberley
jwimberley

Reputation: 1748

Casting a pointer to a struct into another struct type with a smaller number of fields

Basic problem

I'm in a tricky situation that requires taking a pointer to a struct mainset and turning this into a pointer to a struct subset, whose fields are a contiguous subset of the fields of mainset, starting from the first. Is such a thing possible, with well-defined behavior? I realize that this is a pretty terrible thing to do, but I have good and frustrating reasons to do it [explained at the bottom for patient readers].

My attempt an an implementation seems to work, on OS X with the clang compiler:

#include <iostream>

struct mainset {
    size_t size;
    uint32_t reflex_size;
};

struct subset {
    size_t size;
};

using namespace std;
int main(int argc, char *argv[]) {
    mainset test = {1, 1};
    subset* stest = reinterpret_cast<subset*>(&test);
    std::cout << stest->size << std::endl;
}

The output is indeed 1, as I expect. However, I wonder: am I just getting lucky with a particular compiler and a simple case (in reality my structs are more complicated), or will this work in general?

Also, a follow-up question: for other annoying reasons, I worry that I might need to make my larger struct

struct mainset {
    uint32_t reflex_size;
    size_t size;
};

instead, with the extra field coming at the front. Could my implementation be extended to work in this case? I tried replacing &test with &test+sizeof(test.reflex_size) but this didn't work; the output of the cout statement was 0.

Explanation of why I have to do this

My project uses the GSL library for linear algebra. This library makes use of structs of the form

struct gsl_block {
   size_t size;
   double* data;
}

and similar structs like gsl_vector and gsl_matrix. So, I've used these structs as members of my C++ classes; no problem. A recently demanded feature for my project, however, is to enable reflection to my classes with the Reflex tool, part of the ROOT ecosystem. To enable reflection for a struct like this in Reflex, I must add an annotation like

struct gsl_block {
   size_t size;
   double* data; //[size]
}

This annotation tells Reflex that that the length of the array is provided by the field size of the same struct. Normally that would be that, but Reflex and ROOT have a very unfortunate limitation: the length field must be 32 bit. Having been informed that this limitation won't be fixed anytime soon, and not having the time/resources to fix it myself, I'm looking for workarounds. My idea is to somehow embed a struct bit-compatible with gsl_block within a larger struct:

struct extended_gsl_block {
   size_t size;
   double* data; //[reflex_size]
   uint32_t reflex_size;
}

and similar things for gsl_vector and gsl_matrix; I can ensure that reflex_size and size are always equal (neither is ever bigger than ~50) and Reflex will be able to parse this header correctly (I hope; if reflex_size is required to precede data as a field something more difficult would be required). Since GSL routines work with pointers to these structs, my idea is this: given a pointer extended_gsl_block*, somehow get a pointer to just the fields size and data and reinterpret_cast this into a gsl_block*.

Upvotes: 2

Views: 713

Answers (1)

Richard Hodges
Richard Hodges

Reputation: 69864

You are in luck.

The classes you show as an example conform to the requirements of standard layout types.

You can read more here:

http://en.cppreference.com/w/cpp/language/data_members#Standard_layout

You can test this premise in the compiler with:

static_assert(std::is_standard_layout<gsl_block>::value, "not a standard layout");

Upvotes: 2

Related Questions