How do I adapt an old C style #define mapping with modern C++?

Viewed 114

In an old C header file, I have the following mapping (32 lines long):

#define    IOCON_INDEX_PIO0_17       ( 0)
#define    IOCON_INDEX_PIO0_13       ( 1)
#define    IOCON_INDEX_PIO0_12       ( 2)
//...

In C++ I could have a function that takes an index and returns an int. To do so I would initialize an array in only one line, but it wouldn't be very much readable.

I would like to replace it with C++ with a modern styling. I have thought to something using lambdas like this:

#include <array>
class test 
{
    int icon_index(int pio_index)
    {
        const std::array<int, 32> iocon_index = [](){
            std::array<int, 32> buf;
            buf[17] = 0;
            buf[13] = 1;
            buf[12] = 2;
            //...
            return buf;
        }();
        return iocon_index[pio_index];
    }
};

But when I look to the assembly generated code, it seems to be huge. Also I wonder what RAM it will use.

I have to work with a C++14 compliant compiler, so :

  1. What is the best manner to replace this legacy C code with C++14?
  2. What is the best manner to replace this legacy C code with C++17?

constexpr may be a part of the answer...

[edit] I don't want to replace the C code, but adapt it to have a function that takes an int as a parameter and returns another int. It is running on a small embedded system so I want it to be as tight as possible (in terms of flash & RAM).

2 Answers

Using c++17 changing

const std::array<int, 32> iocon_index = [](){
    std::array<int, 32> buf;

to

constexpr std::array<int, 32> iocon_index = []() {
    std::array<int, 32> buf { };

seems to solve the assembly size issue (-O2 and -Os)

In c++14 you can use a constexpr function instead of a lambda to initialize the array.

EDIT:
std::array is not constexpr modifiable in c++14. This is a dirty way to circumvent that issue

std::array<int, 32> buf { };
const auto& as_const = buf;

const_cast<int&>(as_const[17]) = 0; //operator[](size_type) const is constexpr
const_cast<int&>(as_const[13]) = 1; //but not operator[](size_type)
...

EDIT2:
To make it more readable one can do

struct array_assign
{
    constexpr
    array_assign(std::array<int, 32>& arr) :
        arr(arr)
    { }

    constexpr int& operator[](std::size_t idx)
    {
        return const_cast<int&>(this->arr[idx]);
    }

    const std::array<int, 32>& arr;
};

and then

std::array<int, 32> buf2 { };
array_assign buf(buf2);

buf[17] = 0;
buf[13] = 1;
buf[12] = 2;
...
return buf2;

Instead of a lambda you can just create a constexpr:

#include <utility>
int icon_index(int pio_index)
{

    constexpr std::pair<int, int> map_items[] = {
        {10, 1},
        {11, 15},
        {12, 3},
        {13, 32},
        {14, 9},
        {15, 8},
        {16, 9},
        {17, 7},
        {18, 6},
        {19, 5},
        {45, 4}
    };

    for (auto item : map_items)
    {
        if (pio_index == item.first)
        {
            return item.second;
        }
    }

    return 0; /// not found
}

I imagine you can also use std::array - but this is a mapping that you can use any pair of types. Using constexpr means it is generated at compile time.

Updated:

#include <utility>

constexpr int find_item(int idx)
{
    std::pair<int, int> map_items[] = {
        {10, 1},
        {11, 15},
        {12, 3},
        {13, 32},
        {14, 9},
        {15, 8},
        {16, 9},
        {17, 7},
        {18, 6},
        {19, 5},
        {45, 4}
    };

    for (auto item : map_items)
    {
        if (idx == item.first)
        return item.second;
    }

    return 0;
}

int main()
{
    constexpr int i = find_item(12);
    return i; // returns 3
}
Related