4

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();
}
  • So I want to use useThis no matter what cast was done. How can this be done?

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

  • Note tha questions like this have a kind of similar problem but the struct members have the same order

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".

Ivan
  • 1,352
  • 2
  • 13
  • 31
  • 5
    This doesn't address the question, but in C++ you don't have to do the `typedef struct { ... } tstr1;` dance. `struct tstr1 { ... };` works just fine. – Pete Becker Jun 24 '21 at 13:03
  • 2
    Do you expect `MyInstance` to keep the type info after the if/else scope? That'll be _hard_. Otherwise you can trick it to do pretty much everything [example](https://godbolt.org/z/q5G7Kjnrx) or [example](https://godbolt.org/z/93GsKajdW) - but I think this may be an XY problem. – Ted Lyngmo Jun 24 '21 at 13:05
  • seems like it's just an `uint8_t`, would it be doable to just store `useThis` instead of `MyInstance` pointer? or am I missing something? – ben10 Jun 24 '21 at 13:05
  • 1
    `INFO_FROM_HARDWARE` should be some kind of union. Ideally a tagged union. More ideally a `std::variant`. Less ideally a `std::any` – AndyG Jun 24 '21 at 13:08
  • @TedLyngmo yes, `MyInstance` is used a lot, I shouldn't cast it at every call – Ivan Jun 24 '21 at 13:11
  • 1
    @ben10 this is a small example, IRL there are several members and INFO_FROM_HARDWARE is quite large, using directly the `int8_t` would make a mess of a code – Ivan Jun 24 '21 at 13:12
  • @AndyG `INFO_FROM_HARDWARE` is a `uint8_t *` – Ivan Jun 24 '21 at 13:13
  • @Ivan Would dynamic dispatch be ok? – Ted Lyngmo Jun 24 '21 at 14:45
  • @TedLyngmo sounds fancy, I guess yes, but I must admit I don't fully understand what it is – Ivan Jun 24 '21 at 16:25
  • @Ivan I added it as an answer. 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. – Ted Lyngmo Jun 24 '21 at 16:48

4 Answers4

5

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.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 1
    Maybe comment about the two having members of same type and name, but different position, so not really even a common initial sequence to work with for non-templates? – Deduplicator Jun 24 '21 at 13:11
  • 2
    @DanielMcLaury `He's not trying to do static dispatch based on the value of the shared member` That's not what my template does either. The dispatch is based on `someVariable` like in the question. `he just wants a uniform way of accessing that member given either a tstr1 * or a tstr2 *` That's what my template does. It always accesses `useThis` and it depends on the type which member that is. – eerorika Jun 24 '21 at 13:20
  • Hello @eerorika note that I need to acces `MyInstance` in `someFunction()` . so your `do_something()` should return some kind of container? maybe? – Ivan Jun 24 '21 at 13:24
  • @Ivan You'll need to keep the access to `MyInstance` within the template. – eerorika Jun 24 '21 at 13:25
  • or use a variant? no love for variants? :( – ben10 Jun 24 '21 at 13:25
  • 1
    @ben10 Yeah, the `overloaded` trick is pretty neat. It should be included in the standard library. – eerorika Jun 24 '21 at 13:29
  • @ben10 sorry, I should have specified I'm using C++11. My question is getting a bit too case specific – Ivan Jun 24 '21 at 13:31
  • 1
    @Ivan You can use Boost in that case. – eerorika Jun 24 '21 at 13:34
  • or another c++11 compliant variant implementation (abseil comes to mind). or a tagged union? – ben10 Jun 24 '21 at 13:42
4

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...

ben10
  • 221
  • 1
  • 8
3

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;
Nordic Mainframe
  • 28,058
  • 10
  • 66
  • 83
3

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.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • This looks quite cool, could you explain me why "it's going to be slower than if you actually take the time to type cast." ? – Ivan Jun 24 '21 at 16:51
  • 1
    @Ivan I added an explanation but - Swedish midsummer takes its toll and I'm not sure I did a good job explaining it. If it's unclear, please comment and I'll try to improve it in a few days when I'm out of the haze. – Ted Lyngmo Jun 25 '21 at 08:00
  • 1
    this looks a lot like type erasure, clever! – ben10 Jun 25 '21 at 08:40