枚举的编译时查找表

时间:2015-10-27 17:07:14

标签: c++ c++11

我有一个枚举列表,定义如下:

enum PinEnum {
    kPinInvalid,
    kPinA0,
    kPinA1,
    kPinB0,
    kPinB1,
    kPinC0,
    kPinC1,
}

这些枚举中的每一个都需要与另外两个值相关联,即端口和引脚号。目前,我通过运行时功能访问这些:

GPIO_TypeDef * PinGetPort(const PinEnum pin) {
    switch (pin) {
        case kPinA0:
        case kPinA1:
            return GPIOA;
        case kPinB0:
        case kPinB1:
            return GPIOB;
        case kPinC0:
        case kPinC1:
            return GPIOC;
        default:
            return NULL;
    }
}

uint16_t PinGetPin(const PinEnum pin) {
    switch (pin) {
        case kPinA0:
        case kPinB0:
        case kPinC0:
            return GPIO_Pin_0;
        case kPinA1:
        case kPinB1:
        case kPinC1:
            return GPIO_Pin_1;
        default:
            return 0;
    }
}

特别是,我这样做是因为我不希望大型查找表在运行时占用RAM(代码大小不是问题)。

有没有办法使用编译时查找表,constexpr函数或模板构造来执行此操作,以便语句PinGetPin(kPinA0)PinGetPort(kPinA0)各自得到优化单个值而不是必须通过冗长的函数调用和case语句?这些函数的参数将始终为const PinEnum类型,其值在编译时已知。

例如,典型的使用方案如下:

const PinEnum kPinStatus = kPinB0;

int main(int argc, char ** argv) {
    ...
    PinGetPort(kPinStatus)->BSRRH = PinGetPin(kPinStatus);
    // GPIOB->BSRRH = GPIO_Pin_0; <-- should optimize to this during compilation
    ...
}

C ++ 11答案很好。

虽然还有其他编译时查找表的答案,但我没有看到一个直接适用于这种情况的答案。它们要么需要字符串递归,要么实际计算并存储一个查找表(如果没有别的办法,最终可能会出现这种情况)。

4 个答案:

答案 0 :(得分:7)

使用以enum作为参数的模板结构template specializationstd::integral_constant

#include <type_traits>

enum class pin { pin0, pin1 };

template<pin> struct lookup_port;    
template<pin> struct lookup_num;

template<> struct lookup_port<pin::pin0> 
  : std::integral_constant<int, 0> { };

template<> struct lookup_num<pin::pin0> 
  : std::integral_constant<int, 520> { };

template<> struct lookup_port<pin::pin1> 
  : std::integral_constant<int, 22> { };

template<> struct lookup_num<pin::pin1> 
  : std::integral_constant<int, 5440> { };

int main()
{
    static_assert(lookup_port<pin::pin0>::value == 0, "");
    static_assert(lookup_port<pin::pin1>::value == 22, "");

    static_assert(lookup_num<pin::pin0>::value == 520, "");
    static_assert(lookup_num<pin::pin1>::value == 5440, "");
}

在C ++ 14中,由于relaxed constexpr restrictions,您的switch函数可能是constexpr。

答案 1 :(得分:5)

制作一张桌子:

template<class...>struct types {};
template<class lhs, class rhs>struct e{};
template<class types, class lhs>
struct find{};
template<class types, class lhs>
using find_t=typename find<types,lhs>::type;

template<class T0, class R0, class...Ts>
struct find< types<e<T0,R0>,Ts...>, T0>{
  using type=R0;
};
template<class T0, class R0, class...Ts, class lhs>
struct find< types<e<T0,R0>,Ts...>, lhs>:
  find< types<Ts...>, lhs >
{};

使用:

template<PinEnum e>
using PinEnum_t = std::integral_constant<PinEnum, e>;
template<uint16_t i>
using uint16 = std::integral_constant<uint16_t, i>;

using PinGetPin_t = types<
  e<PinEnum_t<kPinA0>, uint16<GPIOA>>,
  e<PinEnum_t<kPinA1>, uint16<GPIOA>>,
  // ...
  e<PinEnum_t<kPinC1>, uint16<GPIOC>>
>;

static_assert( find_t<PinGetPin_t, PinEnum_t<kPinA0>>{}==GPIOA, "oops");

尝试访问上述系统中的无效引脚会导致编译时错误。

live example

我把所有东西放在类型的土地上,并且一对一的地图。也可以使用多对一地图,或者没有PinEnum_t包装等。

答案 2 :(得分:2)

我不确定PinGetPort,因为我们没有GPIO_TypeDefGPIOA等的定义,但PinGetPin应该如果只是将constexpr放在它前面,使用C ++ 14编译器并使用高级优化,那么工作正常。像:

constexpr uint16_t PinGetPin(const PinEnum pin) {

在C ++ 11编译器上,你可能会得到类似的东西:

constexpr uint16_t PinGetPin(const PinEnum pin) {
    return ((pin == kPinA0) || (pin == kPinB0)) ? GPIO_PIN_0 : 
        (((pin == kPinA1) || (pin == kPinB1)) ? GPIO_PIN_1 : 0);
}

但是,正如你所看到的,它变得很难看......

答案 3 :(得分:-3)

您可以使用C数组来定义端口和引脚。然后使用枚举值在数组中访问它们。这使用了默认枚举值是连续的这一事实。当然,这不是编译时间。