我可以将"token pasting operator"与"const"模板参数一起使用吗?

Can I use "token pasting operator" with 'const' template arguments?

本文关键字:一起 参数 const token pasting operator 我可以      更新时间:2023-10-16

我正在尝试为我正在使用的微控制器编写一个通用类。这些野兽使用<register prefix> <index> <suffix>形式的寄存器并不罕见,例如UCSR0BTCCR1A

因此,我编写了宏来连接其参数以形成新令牌:

#define uart_is_enabled(i)      (UCSR ## i ## B)
#define uart_putchar(i, c)      UDR ## i = c

注意:这里我使用的是UART寄存器,但这只是一个示例,我不是要做UART工作,我已经为它编写了代码,我想增强它。

编辑:对于好奇的人,以下是寄存器的定义,例如UCSR0B,根据Atmel库:

#ifndef __SFR_OFFSET
#  if __AVR_ARCH__ >= 100
#    define __SFR_OFFSET 0x00
#  else
#    define __SFR_OFFSET 0x20
#  endif
#endif
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)
#define UCSR0B  _SFR_IO8(0x25)

现在我想尝试模板类来使用这些宏,而无需对每个可能的索引进行专用化:

template <const unsigned index>
class Uart
{
public:
static bool is_enabled() { return uart_is_enabled(index); }
static void putchar(uint8_t) { uart_putchar(index, c); }
};

当然,对于模板参数的uart<0>uart<1>或任何unsigned int,我会收到如下错误消息:

error: 'UCSRindexB' was not declared in this scope

这是因为如果我的理解是正确的,那么在编译模板参数值之前,宏参数会被"评估"(请给我一个迂腐的原谅,因为我使用了不正确的术语(。无论如何,有没有办法做到这一点?


编辑:虽然菲尔的回答100%直接解决了我的问题,但我改变了主意,选择了不同的方法。我认为 C/C++ 是一种您需要冗长的语言,即最好声明您想要管理的所有情况。在某些情况下,像这样,偷工减料可能会产生更繁琐的代码——当然,这只是我的观点,YMMV。

因此,我通过鸭子类型实现了某种形式的硬件抽象:我定义的硬件驱动程序类与我使用的 Atmel 处理器中的许多不同的 UART 一样多 — 我很幸运,Atmel 微控制器中的 UART 类型没有模型数量那么多。

下面是一个包含 LIN/UART 驱动程序类的示例:

// lin.h (excerpt)
// Auto-detect the one and only serial interface (set UART mode)
#ifdef LINDAT
/*
* UART0 driver — for microcontroller which UART module is shared
* with LIN, e.g. ATmega64M1
*/
struct uart0
{
static INLINE void power_on() { power_lin_enable(); }
static INLINE void power_off() { power_lin_disable(); }
static INLINE void enable() { uart_enable(); }
static INLINE void disable() { uart_disable(); }
...
static void reset();
};
#endif

下面是定义 1 个或多个 UART 模块的微控制器的示例:

// uart.h (excerpt)
// Auto-detect first serial interface aka U[S]ART0
#ifdef UDR0
struct uart0
{
static INLINE void power_on() { power_usart0_enable(); }
static INLINE void power_off() { power_usart0_disable(); }
static INLINE void enable() { uart_enable(0); }
static INLINE void disable() { uart_disable(0); }
...
};
#endif

请注意,我仍在使用我在这篇文章中介绍的 C 宏来概括寄存器处理......虽然有些往往变得很难阅读,但我承认。

然后我写了一个模板类(实际上是一个接口类(,该参数是驱动程序类,接口类继承自该类。

// Generic UART wrapper. Comes with a circular input buffer
template <class driver>
class tty : public driver
{
protected:
...
public:
static void putchar(char) { driver::putchar(c); }
static void power_off()
{
driver::disable();
driver::power_off();
}
...
};

根据检测到的寄存器名称包含特定于控制器的头文件:

#if defined(UDR0) || defined(UDR1) || defined(UDR)
#include <drv/uart.h>
#endif
#ifdef LINDAT
#include <drv/lin.h>
#endif

使用此方法,特定于我正在编译的体系结构的驱动程序成员通过接口类公开。它允许我为多个处理器编写相对通用的代码,只要我想支持单个应用程序:

typedef serio tty<uart0>; // same code for ATmega328p, ATmega64M1, ATtiny1634...
serio::putchar('a');

如果我想将我的应用程序专用于特定的微控制器,我也可以编写非常具体的代码。我只需要使用特定于 arch 的驱动程序成员。

对于通用方法,按照惯例,我的所有驱动程序类都必须公开一定数量的公共成员才能使该概念起作用,但只需要执行一次,即在添加对新微控制器的支持时。这确实是某种重复性任务(参见我关于"冗长"的观点(,我的意思是(几乎(一遍又一遍地看起来相同的类型代码(冲洗/重复您想要支持的每个控制器(,但最终这就是我想要的灵活性。

我还检查了生成的汇编代码,我可以确认优化器做得相当出色,尤其是在我要求它内联代码的时间和地点。

以下代码将按预期工作。由于首先评估宏,因此必须为每个寄存器手动创建模板专用化。

// Simulate register names (to test on any compiler)...
static bool UCSR0B;
static bool UCSR1B;
static char UDR0;
static char UDR1;

与原始帖子相同的宏仍然有用

#define uart_is_enabled(i)      (UCSR ## i ## B)
#define uart_putchar(i, c)      UDR ## i = c

然后我们声明一个模板。在这里,我使用一个空类。默认实现可能有所不同。

template <const unsigned index>
class Uart
{
};

此时,我使用宏来定义所有寄存器的专用化。

// Macro that define a template specialization for a specific UART
#define UART(index) 
template <> 
class Uart<index> 
{ 
public: 
static bool is_enabled() { return uart_is_enabled(index); } 
static void putchar(uint8_t c) { uart_putchar(index, c); } 
}
// Define Uart<0>, Uart<1>... classes
UART(0);
UART(1);

最后,这是一个测试程序,表明它按预期工作

// Test program
void test()
{
Uart<0>::is_enabled();
Uart<1>::is_enabled();
// Uart<2>::is_enabled();   // Would not compile
Uart<0>::putchar('a');
Uart<1>::putchar('b');
// Uart<2>::putchar('c'); // Would not compile
}
相关文章: