Ivan
Ivan

Reputation: 1455

Cast structs with certain common members

Lets say I have 2 structs:

typedef struct
{
    uint8_t useThis;            
    uint8_t u8Byte2;             
    uint8_t u8Byte3; 
    uint8_t u8Byte4;  
} tstr1

and

typedef struct
{
    uint8_t u8Byte1;            
    uint8_t u8Byte2;             
    uint8_t useThis;  
} tstr2

I will only need the useThis member inside a function, but in some cases I will need to cast one struct or the other:

void someFunction()
{
    someStuff();
    SOMETHING MyInstance;
    if(someVariable)
    {
        MyInstance = reinterpret_cast<tstr1*>(INFO_FROM_HARDWARE); //This line of course doesn't work
    }
    else
    {
        MyInstance = reinterpret_cast<tstr2*>(INFO_FROM_HARDWARE); //This line of course doesn't work
    }
    MyInstance->useThis; //Calling this memeber with no problem

    moreStuff();
}

EDIT:

In RealLife these structs are way larger and have several "same named" members. Directly casting a uint8_t as reinterpret_cast<tstr1*>(INFO_FROM_HARDWARE)->useThis would be tedious and require several reinterpret_casts (althought it's a working solution for my question before this EDIT). This is why I insist on MyInstance being "complete".

Upvotes: 4

Views: 408

Answers (4)

Ted Lyngmo
Ted Lyngmo

Reputation: 117298

Using virtual dispatch is usually not what you want when mapping to hardware but it is an alternative.

Example:

// define a common interface
struct overlay_base {
    virtual ~overlay_base() = default;
    virtual uint8_t& useThis() = 0;
    virtual uint8_t& useThat() = 0;
};

template<class T>
class wrapper : public overlay_base {
public:
    template<class HW>
    wrapper(HW* hw) : instance_ptr(reinterpret_cast<T*>(hw)) {}
    uint8_t& useThis() { return instance_ptr->useThis; }
    uint8_t& useThat() { return instance_ptr->useThat; }

private:
    T* instance_ptr;
};

With that, you can declare a base class pointer, assign it, and use after the if statement:

int main(int argc, char**) {

    std::unique_ptr<overlay_base> MyInstance;

    if(argc % 2) {
        MyInstance.reset( new wrapper<tstr1>(INFO_FROM_HARDWARE) );
    } else {
        MyInstance.reset( new wrapper<tstr2>(INFO_FROM_HARDWARE) );
    }

    std::cout << MyInstance->useThis() << '\n';
    std::cout << MyInstance->useThat() << '\n';
}

Demo

Explanation regarding my comment: "It works, but unless the compiler is really clever and can optimize away the virtual dispatch in your inner loops, it's going to be slower than if you actually take the time to type cast":

Think of virtual dispatch as having a lookup table (vtable) used at runtime (which is often what actually happens). When calling a virtual function, the program has to use that lookup table to find the address of the actual member function to call. When it's impossible to optimize away the lookup (as I made sure in my example above by using a value only available at runtime) it does take a few CPU cycles extra compared to what you'd get by doing a static cast.

Upvotes: 3

ben10
ben10

Reputation: 241

as suggested by AndyG, how about std::variant (there's no mention of the c++ standard you are using so maybe a c++17 solution is ok - also worth using <insert other variant implementation> if no c++17 available).

here's an example

std::variant knows what type is stored in it and you can use visit anytime you wish to use any of the members in there (snippet here for clarity):

// stolen from @eerrorika (sorry for that :( )
struct hardware {
    uint8_t a = 'A';
    uint8_t b = 'B';
    uint8_t c = 'C';
    uint8_t d = 'D';
};

struct tstr1 {
    uint8_t useThis;            
    uint8_t u8Byte2;             
    uint8_t u8Byte3; 
    uint8_t u8Byte4;  
};

struct tstr2 {
    uint8_t u8Byte1;            
    uint8_t u8Byte2;             
    uint8_t useThis;  
};

// stuff

if(true)
{
    msg = *reinterpret_cast<tstr1*>(&hw);
} 
else
{
    msg = *reinterpret_cast<tstr2*>(&hw);
}

std::visit(overloaded {
    [](tstr1 const& arg) { std::cout << arg.useThis << ' '; }, 
    [](tstr2 const& arg) { std::cout << arg.useThis << ' '; }
}, msg);

EDIT: you can also do a variant of pointers EDIT2: forgot to escape some stuff...

Upvotes: 4

eerorika
eerorika

Reputation: 238321

This is what templates are for:

template<class tstr>
std::uint8_t
do_something(std::uint8_t* INFO_FROM_HARDWARE)
{
    tstr MyInstance;
    std::memcpy(&MyInstance, INFO_FROM_HARDWARE, sizeof MyInstance);
    MyInstance.useThis; //Calling this memeber with no problem
    // access MyInstance within the template
}

// usage
if(someVariable)
{
    do_something<tstr1>(INFO_FROM_HARDWARE);
}
else
{
    do_something<tstr2>(INFO_FROM_HARDWARE);
}

I want to avoid someFunction() to be template (just to avoid this kind of things)

Why can’t I separate the definition of my templates class from its declaration and put it inside a .cpp file?

The linked problem isn't an issue for your use case because the potential set of template arguments is a finite set. The very next FAQ entry explains how: Use explicit instantiations of the template.

Upvotes: 5

Nordic Mainframe
Nordic Mainframe

Reputation: 28737

A simple reference to the struct member might be what you need:

uint8_t &useThis=SomeVariable
 ?reinterpret_cast<tstr1*>(INFO_FROM_HARDWARE)->useThis
 :reinterpret_cast<tstr2*>(INFO_FROM_HARDWARE)->useThis;

Upvotes: 3

Related Questions