Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does "reinterpret_cast" not work as expected on template arguments?

I have the following code example:

#include <iostream>

typedef struct MyStruct {
    int member;
} crake;

#define GPIOA               ( 0x40000000)

template <typename T, crake* Ptr>
struct MyTemplate {
    void doSomething() {
        Ptr->member = 42;
    }
};


int main() {
    crake* ptr = reinterpret_cast<crake*>(GPIOA);
    MyTemplate<MyStruct, reinterpret_cast<crake*>(GPIOA)> instance;
    instance.doSomething();
    return 0;
}

and I compiled it with C++20.Interestingly I get an error on the line

MyTemplate<MyStruct, reinterpret_cast<crake*>(GPIOA)> instance;

but not on the line before, where I do the same thing, namely reinterpret casting an integer to a pointer.

The exact error message says:

"error: ‘reinterpret_cast’ from integer to pointer
   29 |     MyTemplate<MyStruct, reinterpret_cast<crake*>(GPIOA)> instance;"

My goal is to get the address of a struct defined as a constant value (with #define) as a non-type template parameter. I know this would crash, but in reality, there is a peripheral behind the address, and writing to it is ok. So I assume, I need to use a const variable but I wanted to avoid that.

like image 708
Benjamin Graef Avatar asked Oct 15 '25 09:10

Benjamin Graef


1 Answers

Template arguments like in:

MyTemplate<MyStruct, reinterpret_cast<crake*>(GPIOA)>

... have to be constant expressions. reinterpret_cast cannot be used in constant expressions. In this case, the problem with allowing it would be that you are creating a pointer to an object that doesn't exist at compile time, so the template argument is somewhat meaningless. The memory which exists at compile time is different from the memory at runtime.

However, you can pass an address as a template argument, and perform the conversion at runtime:

#include <iostream>
#include <cstddef>

struct crake {
    int member;
};

inline constexpr std::uintptr_t GPIOA = 0x40000000;

template <typename T, std::uintptr_t Address>
struct MyTemplate {
    static inline crake * const Ptr = reinterpret_cast<crake*>(Address);
    void doSomething() {
        Ptr->member = 42;
    }
};


int main() {
    MyTemplate<crake, GPIOA> instance;
    instance.doSomething();
    return 0;
}

This has no additional overhead compared to the original code, and compiles to:

main:
   mov dword ptr [0x40000000], 42
   xor eax, eax
   ret

See live example on Compiler Explorer.

like image 154
Jan Schultke Avatar answered Oct 17 '25 23:10

Jan Schultke



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!