using an absolute pointer address as a template argument

Casting to/from ints works, but as pointed out, it's dangerous. Another solution similar to JimmyB's is to use enum classes instead of function pointers. The enum class member values are set to the device addresses as specified inthe vendor-supplied header. For instance, for the STM32 series, ST provides a header with the following defined:

// Vendor-supplied device header file (example)

#define GPIOA_BASE = 0x40001000
#define GPIOB_BASE = 0x40002000
//    etc...

In your code, create an enum class:

#include <vendor-supplied-device-header.h>

enum class GPIO : uint32_t {
    A = GPIOA_BASE, 
    B = GPIOB_BASE, 
    C = GPIOC_BASE, 
    D = GPIOD_BASE, 
    E = GPIOE_BASE,
    F = GPIOF_BASE,
    G = GPIOG_BASE,
    #ifdef GPIOH_BASE   //optional: wrap each member in an #ifdef to improve portability
    H = GPIOH_BASE,
    #endif
    //.. etc
};

To avoid multiple messy casts, just do it once in the class using a private method. For example then your LedToggle class would be written like this:

template<GPIOPORT PORT, uint8_t PIN, uint32_t RATE> class LedToggle
{
    static_assert(PIN < 15, "Only pin numbers 0 - 15 are valid");

    volatile auto GPIOPort(GPIOPORT PORT) {
        return reinterpret_cast<GPIO_TypeDef *>(port_);
    }

    uint32_t mTicks;
    uint32_t mSetReset;

    public:

    LedToggle()
    {
        mTicks = 0;
        mSetReset = 1 << PIN;
    }

    void Update()
    {
        uint32 mask = ((mTicks++ & RATE) - 1) >> 31;
        GPIOPort(PORT)->BSRR = mSetReset & mask;
        mSetReset ^= ((1 << PIN) | (1 << (PIN + 16))) & mask;
    }
};

LedToggle<GPIO::C, 13, 1023> led;

The benefit of this method is that the class users are forced to use only members of the GPIO enum class, therefore invalid addresses are prohibited.

You can use enum classes for any of the template parameters, for instance you could replace the PIN parameter with an enum class whose members are set to the vendor's specified GPIO_PIN_1, GPIO_PIN_2, etc. Then you'd write:

LedToggle<GPIO::C, Pin::_13, 1023> 

Facing the same problem (on an STM32), as a work-around I found function pointer template parameters, like so:

template<GPIO_TypeDef* PORT(), uint32 BIT, uint32 RATE>
class LedToggle
{
    public:

    void Update()
    {
        // ...
        PORT()->BSRR = mSetReset & mask;
        // ...
    }
};

constexpr GPIO_TypeDef* Port_C() {
  return PORTC;
}

LedToggle<Port_C, 13, 1023> led;

Notice that we use a function pointer as template parameter, to a function that returns the desired actual pointer. Inside that function casts are allowed; and since the function is declared constexpr the compiler may (should) optimize away the actual function call and use the function's return value like a literal.


The declaration bar<(foo*)0x80103400> myFoo; is ill-formed because non-type template arguments must be a constant expression, from [temp.arg.nontype]:

A template-argument for a non-type template-parameter shall be a converted constant expression (5.20) of the type of the template-parameter.

And the argument you are passing is not, from [expr.const]:

A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (1.9), would evaluate one of the following expressions:
— [...]
— a reinterpret_cast (5.2.10);
— [...]

The declaration bar<(foo*)0> huh works since it does not involve a cast, it's simply a null pointer of type foo* (0 is special) and so it is a valid constant expression.


You could instead simply pass in the address as a template non-type parameter:

template <uintptr_t address>
struct bar { ... };

bar<0x8013400> myFooWorks;

That is viable.