using an absolute pointer address as a template argument

前端 未结 6 2454
攒了一身酷
攒了一身酷 2021-02-19 22:49

I have a template class which takes as its first template argument a foo * pointer. I\'d like to instantiate one of these with a foo located at an absolute address, like so:

相关标签:
6条回答
  • 2021-02-19 23:34

    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.

    0 讨论(0)
  • 2021-02-19 23:37

    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.

    0 讨论(0)
  • 2021-02-19 23:38

    From the C++11 Standard (emphasis mine):

    14.3.2 Template non-type arguments

    1 A template-argument for a non-type, non-template template-parameter shall be one of:

    — for a non-type template-parameter of integral or enumeration type, a converted constant expression (5.19) of the type of the template-parameter; or

    — the name of a non-type template-parameter; or

    — a constant expression (5.19) that designates the address of an object with static storage duration and external or internal linkage or a function with external or internal linkage, including function templates and function template-ids but excluding non-static class members, expressed (ignoring parentheses) as & id-expression, except that the & may be omitted if the name refers to a function or array and shall be omitted if the corresponding template-parameter is a reference; or

    — a constant expression that evaluates to a null pointer value (4.10); or

    When compiled with g++ -Wall, I get the following error:

    error: ‘2148545536u’ is not a valid template argument for ‘foo*’ because it is not the address of a variable
        bar<FOO_ADDR> myFoo;        // fails with non-integral argument
                           ^ 
    

    It seems that the compiler is able to detect that 0x80103400 is not the address of a variable, it's just a constant expression.

    0 讨论(0)
  • 2021-02-19 23:39

    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> 
    
    0 讨论(0)
  • 2021-02-19 23:46

    Have you try casting the pointer address to and from uintptr_t, which is an unsigned integer capable of holding a pointer value? The type is defined in a standard header <cstdint> but it is only optional. If it does not exist in your version of C++, try size_t instead.

    A full example would then look like:

    #include <cstdint>
    
    class foo
    {
        int baz;
    };
    
    template<uintptr_t addr> class bar
    {
        constexpr static const foo* f = (foo*)(addr);
    public:
        bar() {}
        void update() {  }
    };
    
    #define FOO_ADDR ((uintptr_t)0x80103400)
    
    int main() {
        bar<FOO_ADDR> myFoo;
    }
    

    The obvious drawback is that there is no typechecking at the template parameter. You pass a value which will hopefully refer to foo object and not a different one.

    Not to mention that we are in the undefined behavior world as far as the standard goes....


    You seem to be able to compile with the line

    constexpr static const foo* f = reinterpret_cast<foo*>(addr);
    

    with at least some of the compilers as well (http://coliru.stacked-crooked.com/a/5af62bedecf2d75a)


    If your compiler rejects casting in the context of constexpr as it is ill-formed (as per Barry's comment) you can define it as a regular static const variable:

    template<uintptr_t addr>
    class bar
    {
        static const foo* f;
    public:
        bar() {}
        void update() {  }
    };
    
    template<uintptr_t addr>
    const foo* bar<addr>::f = reinterpret_cast<foo*>(addr);
    

    Less ideal but solves that problem hopefully.

    0 讨论(0)
  • 2021-02-19 23:47

    First, don't use c-style casts. It doesn't make it explicit what is does.

    Second, you will realize that in order to cast an arbitrary number into a pointer, you need to use reinterpret_cast. This sort of cast is not allowed in constexpr expressions.

    Since template parameters are only possible with constant expressions, an arbitrary number as a pointer is not possible.

    The case where 0 is possible is because 0 is convertible to nullptr, which is constant expression.

    0 讨论(0)
提交回复
热议问题