【问题标题】:Can't make C++ compiler throw an error using type traits conditionally不能使 C++ 编译器有条件地使用类型特征引发错误
【发布时间】:2020-07-01 20:53:13
【问题描述】:

我正在尝试学习/评估用于嵌入式编程的 C++,但遇到了一个问题,我似乎无法自己解决。我想使用类型特征来描述特定微控制器引脚的不同可能用途。然后我想让编译器停止编译我的项目,如果一个引脚用于它实际上不能做的事情。我的问题是它要么只在运行时工作,要么编译器产生误报:即它坚持一个引脚不能用于某些功能,即使我从未尝试使用它,但只有作为其他东西(比如驱动 LED)......

请原谅这篇冗长的帖子 - 我希望你能忍受我的解释并帮助我找到解决方案。

我已经使用模板为 MCU 引脚创建了一个实现,如下所示:

enum class pin_funciton {
    OUTPUT, INPUT, INTERRUPT_FALLING,
    INTERRUPT_RISING, INTERRUPT_LEVEL,
    ANALOG, SPI, UART, };

// base class is needed so instance can be passed as function arguments
class gpio {
public:
    virtual void configure(pin_function, unsigned char startupLogic) = 0;
    virtual void set() {}
    virtual void reset() {}
    /* ... other functions like read() etc. */
};

template<unsigned int GPIO_BASE, unsigned int pin>
class gpio_pin : public gpio {
public:
    void configure(pin_function, unsigned char startupLogic = 0U) override {
        /* actual implementation based on options */
    }
    // other methods are implemented as well
};

void do_stuff(gpio* signalPin) {
    // do some very super duper useful work
}

// other usage
gpio_pin<GPIOB, 4> redLed;
redLed.set();

到目前为止,这一切都很好。输入类型特征。当引脚被配置为使用时,类型特征应该通知编译器引脚是否可以这样使用。这是一个可能的 trait 实现示例:

template<unsigned int port, unsigned int pin>
struct is_spi_pin {
    static constexpr bool allow() {
        switch (port) {
            case GPIOE_BASE: if (pin == 1 || pin == 2) return true; break;
            case GPIOC_BASE: if (pin == 1 || pin == 2 || pin == 3) return true; break;
            case GPIOB_BASE: if (pin == 10 || pin == 12 || pin == 14) return true; break;
            default: return false;
         }
     }
};

这是我遇到的第一个并发症。我知道典型的约定是通过模板专业化来实现这一点,即首选方式是这样的:

template<unsigned int port, unsigned int pin>
struct is_spi_pin {
    static const bool value = false;
};

template<>
struct is_spi_pin<GPIOE_BASE, 1> {
    static const bool value = true;
};

template<>
struct is_spi_pin<GPIOE_BASE, 2> {
    static const bool value = true;
};
/* rest of the SPI pins specializations ... */

但是,我不想为 MCU 的所有引脚的所有可能使用类型实现专门化,所以我认为我可以使用 constexpr 函数。我可以为每种使用类型设置一个功能,这样可以节省一些时间和工作量。请告知,如果我的解决方案是不可取的,出于什么原因......

然后,这是让我很适合的用例......

我有另一个类似的 SPI 外设模板类:

enum class spi_type {
    SPI_TYPE0, SPI_TYPE1, SPI_TYPE2, SPI_TYPE4
};

class spi {
public:
    virtual void configure(unsigned int speed, spi_type spiType) = 0;
    virtual unsigned char write(unsigned char dat) = 0;
    virtual void write(unsigned char* buffer, size_t bufferSz) = 0;
    virtual void read(unsigned char* dest, size_t  destSz) = 0;
};

template<unsigned int SPI_BASE>
class spi_periph : public spi {
public:
    spi_periph(gpio* clkPin, gpio* mosiPin, gpio* misoPin)
        : clk_(clkPin), mosi_(mosiPin), miso_(misoPin) { }
    void configure(unsigned int speed = 2000000LU,
                   spi_type spiType = spi_type::SPI_TYPE0) override {
        /* ... */
    }
    // other functions ...
private:
    gpio *clk_{ nullptr }, *mosi_{ nullptr }, *miso_{ nullptr };
};

// usage
gpio_pin<GPIOA, 5> errorLed{};
errorLed.configure(pin_function::OUTPUT);

gpio_pin<GPIOC, 0> clk{};
gpio_pin<GPIOC, 3> mosi{};
spi_periph<SPI0> spi0{ &clk, &mosi, nullptr };
spi0.configure(/*...*/);

spi::configure 函数中,实现调用gpio::confugure 在所有引脚上使用pin_function::SPI(如果不是nullptr)。如果 gpio 类型特征表明时钟、mosi 或 miso 引脚不能是 SPI 引脚,我希望编译器抛出错误。

这是我对gpio::configure 函数的实现:

template<unsigned int port, unsigned int pin>
void gpio_pin<port, pin>::configure(pin_function useAs, unsigned char initialLogic) {
    switch (useAs) {
        case pin_function::SPI:
            assert(is_spi_pin<port, pin>::allow());
            // configure as SPI pin
            break;
        case pin_function::OUTPUT:
            // configure as output
            break;
        /* other cases */
    }
}

不幸的是,这仅在运行时有效。如果我用 static_assert 替换断言,则编译器不会将 clk 和 mosi 实例标记为错误,如果类型特征表明它们是 SPI 引脚。但是 它会将 errorLed.configure 的调用标记为错误,如果 GPIOA 引脚 5 不是 SPI 引脚,即使我没有尝试使用它!

这可以解决吗???如果是,那么如何?

感谢您阅读所有这些内容,当然,感谢您提供的所有帮助!

【问题讨论】:

  • 我认为问题的症结在于void do_stuff(gpio* signalPin)。你有什么神奇的功能可以在 all 引脚上工作?我会将其作为实现细节隐藏起来,并且没有 gpio 类。
  • 如果你绝对必须有一个基类,那么问题就是spi_periph(gpio* clkPin, gpio* mosiPin, gpio* misoPin)。如果clkPin 必须是SPI 引脚,则使其仅接受SPI 引脚,而不是所有gpio 引脚。
  • 但是如果没有minimal reproducible example,就很难理解您面临的问题,因此也很难提出解决方案
  • @MooingDuck,感谢您的 cmets。 void do_stuff(gpio* signalPin)template&lt;unsigned int SPI_BASE&gt; spi_periph&lt;SPI_BASE&gt;::spi_periph(gpio* clk, gpio* mosi, gpio* miso) 没有太大区别。由于template&lt;unsigned port, unsigned pin&gt; class gpio_pin 在实例化之前不是一个类型,所以我不能使用 is 作为 spi 类构造函数的参数。据我了解,我必须为此使用基类...
  • template&lt;unsigned int SPI_BASE&gt; template&lt;unsigned port, unsigned pin&gt; spi_periph&lt;SPI_BASE&gt;::spi_periph(gpio_pin&lt;port, pin&gt;* clk, gpio* mosi, gpio* miso) 工作得很好。

标签: c++ templates embedded template-specialization typetraits


【解决方案1】:

不幸的是,这仅在运行时有效。如果我用 static_assert 替换断言,那么编译器不会将 clk 和 mosi 实例标记为错误,如果类型特征表明它们是 SPI 引脚。

当然。

如果你可以使用C++17,并且你可以将useAs作为模板参数传递,你可以解决if constexpr链的问题。

所以,而不是

switch (useAs) {
    case pin_function::SPI:
        assert(is_spi_pin<port, pin>::allow()); // <-- compilation error if static_assert
        // configure as SPI pin
        break;
    case pin_function::OUTPUT:
        // configure as output
        break;
    /* other cases */
}

你可以写

if constexpr ( pin_function::SPI == useAs )
 {
   static_assert(is_spi_pin<port, pin>::allow());
   // configure as SPI pin
 }
else if constexpr ( pin_function::OUTPUT == useAs )
 {
   // configure as output
 }

注意您必须将useAs 作为模板参数传递,否则编译器无法编译is constexprs。

问题在于,如果您使用switch/case(或简单的if 链),编译器必须编译所有内容,即使不使用时也是如此。因此,您的 alert() 有效,因为仅在运行时检查(并且仅当 useAspin_function::SPI 时)但您的 static_assert() 给出编译错误,因为当 useAspin_function::SPI 不同时也检查编译时间

if constexpr的存在正是为了避免此类问题,并在编译阶段削减未使用的代码。

如果没有 C++17...您必须针对不同的情况开发不同的方法(或接受 assert() 工作运行时)。

【讨论】:

  • 您的解决方案有效,但是... 要使用模板参数,它必须是类模板参数的一部分,或者配置函数必须是模板函数。由于配置功能是虚拟的,它不能是模板功能(有什么办法吗?)。在另一种情况下,它可以工作,但有一个不幸的副作用。由于它现在是实例类型的一部分,它阻止我多次configure 同一个实例并更改 pin 的使用方式。当输入或输出引脚需要变为高阻抗时,这将很有用。
猜你喜欢
  • 1970-01-01
  • 2012-11-27
  • 1970-01-01
  • 2012-10-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-05
相关资源
最近更新 更多