6

I have a list of enums which are defined as follows:

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

Each of these enums needs to be associated with two other values, the port and the pin number. Currently, I'm accessing these through run-time functions:

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

In particular, I'm doing this because I do not want a large lookup table to be taking up RAM at runtime (code size is much less of an issue).

Is there a way to do this using a compile-time lookup table, constexpr function, or a template construct so that the statements PinGetPin(kPinA0) and PinGetPort(kPinA0) each get optimized to a single value instead of having to go through a lengthy function call and case statement? The arguments to these functions will always be of type const PinEnum with values known at compile time.

For example, a typical usage scenario is the following:

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 answers are fine.

While there are other answers out there for compile-time lookup tables, I do not see one which would directly apply to this case. They either require string recursion, or actually calculate and store a lookup table (which this may end up coming to if there's no other way).

devtk
  • 1,999
  • 2
  • 18
  • 24
  • 2
    _"They either require string recursion, or actually calculate and store a lookup table (which this may end up coming to if there's no other way)."_ .... which is why this is a duplicate. This _has_ been asked before. The fact that the answers aren't what you'd hoped for doesn't change the fact that they're the best answers, as you've just conceded! – Lightness Races in Orbit Oct 27 '15 at 17:36
  • 1
    @LightnessRacesinOrbit "I want to do X without string recursion or a stored (in data) lookup table, how?" is a different question that "I want to do X, how?" in that answers to the second do not always answer the first. ;) – Yakk - Adam Nevraumont Oct 27 '15 at 20:14
  • @Yakk I agree in general, but when 50,000 answers have already said "the only way to do this is with string recursion or a stored (in data) lookup table", I don't think we need another question on the topic. :) – Lightness Races in Orbit Oct 28 '15 at 00:06

4 Answers4

8

Use template structs taking enum as a parameter, template specialization, and std::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, "");
}

In C++14, your switch function could be constexpr, thanks to relaxed constexpr restrictions.

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 1
    Thanks for the note about C++14, that may be the best solution if my compiler actually supports it in this usage. – devtk Oct 27 '15 at 17:47
  • 1
    @devtk not that a `constexpr` function need not be evaluated at compile time. And a non-`constexpr` function can be evaluated at compile time. `constexpr` just means "you are allowed to try to use the result of this function as non-type template parameter or array size or similar". For a high probability of evaluation at compile time, having the value live as a non-type template parameter to a template is best. – Yakk - Adam Nevraumont Oct 27 '15 at 18:05
5

Make a table:

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

use:

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");

attempting to access an invalid pin in the above system results in a compile time error.

live example.

I kept everything in the land of types, and one-to-one maps. A many-to-one map, or not having the PinEnum_t wrappers, etc, is also possible.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
2

I'm not sure about PinGetPort, as we don't have a definition of GPIO_TypeDef and GPIOA et al, but, PinGetPin should work fine if you just put constexpr in front of it, use a C++14 compiler and use a high level of optimization. Like:

constexpr uint16_t PinGetPin(const PinEnum pin) {

On a C++11 compiler you might get away with something like:

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

But, as you can see, it gets ugly fast...

srdjan.veljkovic
  • 2,468
  • 16
  • 24
-3

You could use C arrays to define the port and pin. And then use the enum value to access them in the array. This uses the fact that default enum values are sequential. Of course, this is not compile time.

Anon Mail
  • 4,660
  • 1
  • 18
  • 21
  • Actually, no answer (The question mentions "calculate and store a lookup table"), but the sensible thing, to do. See also the comment of @Lightness Races in Orbit. –  Oct 27 '15 at 18:00