使用绝对指针地址作为模板参数

时间:2016-05-18 15:26:01

标签: c++ templates pointers

我有一个模板类,它将第一个模板参数作为foo *指针。我想用一个位于绝对地址的foo来实例化其中一个,如下所示:

class foo
{
    int baz;
};

template<foo *f> class bar
{
public:
    bar() {}
    void update() { /* ... */ }
};

// ....

#define FOO_ADDR ((foo *)0x80103400)
#define FOO_NULL ((foo *)0)

foo testFoo;

bar<FOO_ADDR> myFoo;        // fails with non-integral argument
bar<FOO_NULL> huh;          // compiles, I was surprised by this
bar<&testFoo> test;         // compiles as expected (but not useful)

有没有人知道是否可以不借助链接器并使用外部链接定义FOO_ADDR?

这是使用Keil ARM C / C ++编译器版本V5.06更新1(版本61),我尝试切换C ++ 11模式但(除了在系统头中抛出大量新错误)它没有改变行为。

更新:这是使用int casts

提出的解决方案(这次使用实际代码)
template<uint32 PORT, uint32 BIT, uint32 RATE> class LedToggle
{
    uint32 mTicks;
    uint32 mSetReset;

    public:

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

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

LedToggle<(uint32)GPIOC, 13, 1023> led;

这很丑陋,但确实有效。我有兴趣听听是否有人可以改进它?

6 个答案:

答案 0 :(得分:5)

声明bar<(foo*)0x80103400> myFoo;格式错误,因为非类型模板参数必须是[temp.arg.nontype]的常量表达式:

  

非类型模板参数 template-argument 应为 template-parameter <类型的转换常量表达式(5.20) / em>的

你传递的论点不是,来自[expr.const]:

  

条件表达式e是核心常量表达式,除非e的评估遵循规则   抽象机器(1.9),将评估以下表达式之一:
   - [...]
   - reinterpret_cast(5.2.10);
   - [...]

声明bar<(foo*)0> huh有效,因为它不涉及强制转换,它只是类型为foo*的空指针(0是特殊的),因此它是一个有效的常量表达式。

您可以简单地将地址作为模板非类型参数传递:

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

bar<0x8013400> myFooWorks;

这是可行的。

答案 1 :(得分:2)

面对相同的问题(在STM32上),作为一种解决方法,我发现了函数指针模板参数,如下所示:

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;

请注意,我们使用 function 指针作为模板参数,指向返回所需实际指针的函数。在该函数内部允许强制转换;并且由于函数已声明为constexpr,因此编译器可以(应该)优化实际的函数调用,并像文字一样使用函数的返回值。

答案 2 :(得分:1)

向int或从int进行铸造是可行的,但是如前所述,这很危险。类似于JimmyB的另一种解决方案是使用枚举类代替函数指针。枚举类成员值设置为供应商提供的标头中指定的设备地址。例如,对于STM32系列,ST提供了一个标头,其定义如下:

// Vendor-supplied device header file (example)

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

在您的代码中,创建一个枚举类:

#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
};

为避免多次混乱的转换,只需在类中使用私有方法执行一次即可。例如,然后您的LedToggle类将这样编写:

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;

此方法的好处是,类用户被迫仅使用GPIO枚举类的成员,因此禁止了无效地址。

您可以对任何模板参数使用枚举类,例如,可以将PIN参数替换为其成员设置为供应商指定的GPIO_PIN_1,GPIO_PIN_2等的枚举类。然后您将编写:

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

答案 3 :(得分:0)

您是否尝试将指针地址转换为uintptr_t和来自<cstdint>,这是一个能够保存指针值的无符号整数? 类型在标准头size_t中定义,但它只是可选的。如果您的C ++版本中不存在,请尝试使用#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; }

一个完整的例子如下:

foo

明显的缺点是模板参数没有类型检查。您传递的值有望引用constexpr static const foo* f = reinterpret_cast<foo*>(addr); 对象,而不是另一个。

更不用说我们处于未定义的行为世界,就标准而言....

您似乎可以使用

行进行编译
constexpr

至少还有一些编译器(http://coliru.stacked-crooked.com/a/5af62bedecf2d75a

如果您的编译器在static const的上下文中拒绝转换,因为它是格式错误的(根据Barry的评论),您可以将其定义为常规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); 变量:

var BugList = require('./components/bugList');

不太理想,但希望能解决这个问题。

答案 4 :(得分:0)

从C ++ 11标准(强调我的):

  

14.3.2模板非类型参数

     

1非类型非模板模板参数 template-argument 应为以下之一:

     

- 对于整数或枚举类型的非类型模板参数 template-parameter 类型的转换常量表达式(5.19);或

     

- 非类型模板参数的名称;或

     

- 一个常量表达式(5.19),指定具有静态存储持续时间和外部或内部链接的对象的地址或具有外部或内部链接的函数,包括函数模板和函数 template-ids 但不包括非静态类成员,表示(忽略括号)为& id-expression ,但如果&可能省略g++ -Wall name是指函数或数组,如果相应的 template-parameter 是引用,则应省略;或

     

- 一个常量表达式,其值为空指针值(4.10);或

使用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 ^ 编译时,出现以下错误:

0x80103400

似乎编译器能够检测到0x80000000 变量的地址,它只是一个常量表达式。

答案 5 :(得分:-2)

首先,不要使用c风格的演员表。它没有明确说明是什么。

其次,您将意识到为了将任意数字转换为指针,您需要使用reinterpret_cast。在constexpr表达式中不允许这种强制转换。

由于模板参数只能用常量表达式,因此不能使用任意数字作为指针。

0可能的情况是因为0可以转换为nullptr,这是常量表达式。