3

I want to use an IO-Register ( == static memory address ) as a template parameter. The problem is, that registers are commonly defined as macros expanding to something similar to (*(volatile uint8_t*)(11 + 0x20)) which I somehow cannot get to work with my templates properly.

I would like to write code like:

Foo<PORTB> foo;

That way I can easily change the IO register the class uses without any runtime overhead (which is crucial for microcontrollers). I have included a full example below:

#include <stdint.h>
#include <stdio.h>
#include <utility>

#define PORTB  (*(volatile uint8_t*)(11 + 0x20))

template<volatile uint8_t* PortRegister>
class ControllerPtr final
{
public:
    static void SetHigh() { *PortRegister |= 0x2; }
};

template<volatile uint8_t& PortRegister>
class ControllerRef final
{
public:
    static void SetHigh() { PortRegister |= 0x2; }
};


int main()
{
    ControllerPtr<&PORTB> ptr;
    ControllerRef<PORTB> ref;

    ptr.SetHigh();
    ref.SetHigh();

    // Both statements should be equal to:
    // PORTB |= 0x2;
}

Everytime when I try to pass &PORTB to ControllerPtr, g++ fails to compile:

error: (volatile uint8_t*)((long int)(11 + 32)) is not a valid template argument for volatile uint8_t* {aka volatile unsigned char*} because it is not the address of a variable

error: expression *(volatile uint8_t*)((long int)(11 + 32)) has side-effects

The error is a little bit different when trying to pass PORTB to a reference type like used in ControllerRef:

error: *(volatile uint8_t*)((long int)(11 + 32)) is not a valid template argument for type volatile uint8_t& {aka volatile unsigned char&} because it is not an object with linkage

I actually don't understand why this error is an error, as I don't see any problem with passing static addresses to templates.

2 Answers 2

6

A non-type template parameter has to be a constant expression, and you cannot have a reinterpret_cast inside a constant expression (unless it is unevaluated).

Since you've indicated that you have no way of getting access to the numeric address other than through macros such as PORTB, I suggest a workaround. Though PORTB cannot be used in a template parameter, we can synthesize a unique type that can be used in a template parameter, like so:

struct PORTB_tag {
    static volatile uint8_t& value() { return PORTB; }
};
template <class PortTag>
class ControllerRef final {
  public:
    static void SetHigh() { PortTag::value() |= 0x2; }
};
int main() {
    ControllerRef<PORTB_tag> ref;
    ref.SetHigh();
}

To save repetitive typing when you have lots of ports, we can use macros:

#define PORT_TAG(port) port_tag_for_ ## port
#define MAKE_PORT_TAG(port) struct port_tag_for_ ## port { \
    static volatile uint8_t& value() { return port; } \
}
template <class PortTag>
class ControllerRef final {
  public:
    static void SetHigh() { PortTag::value() |= 0x2; }
};
MAKE_PORT_TAG(PORTB);
int main() {
    ControllerRef<PORT_TAG(PORTB)> ref;
    ref.SetHigh();
}

http://coliru.stacked-crooked.com/a/401c0847d77ec0e0

Sign up to request clarification or add additional context in comments.

1 Comment

This isn't actually possible, since I have no direct access to the underlying address of register defines like PORTB. Those are macros which only ultimately evaluate to e.g. *(volatile uint8_t*)(11 + 0x20). I cannot change them and have to use the macro to obtain the address. So in your example I would need a way to declare the constexpr portb variable only by using PORTB and not the address it evaluates to.
0

As Brian pointed out in his answer, the template parameter has to be a constant expression. In somewhat older GCCs (pre-6.0), the following hack should work:

#include <stdint.h>
#include <stdio.h>
#include <utility>

#define PORTB (*(volatile uint8_t*)(11 + 0x20))

// Helper constant for 0 address
#define ZERO  (*(volatile uint8_t*)(0))

// A ptrdiff_t is OK
template <std::ptrdiff_t PortRegister>
class ControllerPtr final
{
public:
  // Get back to the actual desired address
  static void SetHigh() { *(volatile uint8_t*)(&ZERO+PortRegister) |= 0x2; }
};

// Define helper constants for all relevant registers
static constexpr std::ptrdiff_t C_PORTB = &PORTB-&ZERO;

int main()
{
  // Now this works
  ControllerPtr<C_PORTB> ptr;

  ptr.SetHigh();
}

Inspect the result here. I'm calling it a hack because newer GCC versions will (rightly) refuse to compile this with the error

error: reinterpret_cast from integer to pointer

at the line of the constexpr declaration, because technically you cannot use the result of a reinterpret_cast in a constant expression. This SO post provides a bit more background.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.