tinkering with c++ — replacing boost array with standard c++ array

Boost does more than provide a collection of advanced libraries for the C++ language. Many of Boost’s authors are also part of the C++ standards committee. Thus, many new features being considered for the C++ standard are implemented here first. One of those features that has now migrated into the official standard is boost::array, which as of C++11 is now std::array. The page for boost::array has the following line on its documentation front page:

Update: std::array is (as of C++11) part of the C++ standard. The differences between boost::array and std::array are minimal. If you are using C++11, you should consider using std::array instead of boost::array.

The following code shows some of the ways this container is used. If you change the #include <array> to <boost/array>, as well as the corresponding using statement, then it will compile and execute the same.

#include <array>#include <algorithm>#include <iostream>using std::array;using std::cout;using std::sort;using std::string;int main() {array<int, 10> iarray = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};cout << "Array of integers:" << "\n";for (const auto value : iarray) {cout << "Integer array value: " << value << "\n";}cout << "\n" << "Array of standard strings:" << "\n";array<string, 10> sarray ={"one","two","three","four","five","six","seven","eight","nine","ten"};for (const auto value : sarray) {cout << "String array value: " << value << "\n";}cout << "\n" << "Array of sorted strings:" << "\n";sort(sarray.begin(), sarray.end());for (const auto value : sarray) {cout << "String array value: " << value << "\n";}return 0;}

Compiling It

Compilation is a simple one liner.

g++ -std=c++20 array-test.cpp -o array-test

Sample Run

Here’s what happens when we run it.

~ ./array-test Array of integers:Integer array value: 1Integer array value: 2Integer array value: 3Integer array value: 4Integer array value: 5Integer array value: 6Integer array value: 7Integer array value: 8Integer array value: 9Integer array value: 10Array of standard strings:String array value: oneString array value: twoString array value: threeString array value: fourString array value: fiveString array value: sixString array value: sevenString array value: eightString array value: nineString array value: tenArray of sorted strings:String array value: eightString array value: fiveString array value: fourString array value: nineString array value: oneString array value: sevenString array value: sixString array value: tenString array value: threeString array value: two

Comments

What makes std::array better than std::vector is that there is no need for extra code to support a dynamic container like vector. The container array is like the simpler C fixed array, in that it’s fixed and static, but has enough container traits that it can be used in a C++ ranged for loop (lines 15,23, and 29), as show in the example above.

Using std::array in an Embedded Application

I have been writing ESP32-S3 and ESP32-C3 C++ code. I’ve published the following code more than once, but this time, I just want to highlight how I replaced the C++ standard library vector and tuple type with array. Here is where std::array helps in dropping the size of the binary. I checked the size of the generated binary (everything else the same) and discovered that I save a bit of 10K of generated binary, meaning that I saved 10K by switching to array. What’s more the ability to access the elements of the inner array in line 44 makes a lot more sense than the older way of declaring the inner colors a tuple and the convoluted way I accessed tuple members the way I did.

#include <stdio.h>#include <array>#include "freertos/FreeRTOS.h"#include "freertos/task.h"#include "driver/gpio.h"#include "esp_log.h"#include "led_strip.h"#include "sdkconfig.h"using std::array;const array<array<int, 3>, 7> colors {{{32,0,0},  // red{0,32,0},  // green{0,0,32},  // blue{0,32,32}, // cyan{32,0,32}, // magenta{32,16,0}, // yellow{0,0,0}// black}};// Task 1.//static void task_blink_neo_pixel(void * pvParameters) {static led_strip_t *pStrip_a;pStrip_a = led_strip_init(CONFIG_BLINK_LED_RMT_CHANNEL, CONFIG_BLINK_GPIO, 1);pStrip_a->clear(pStrip_a, 50);// Stay in an endless loop. Don't return from this task.//while(true) {for(auto color : colors) {pStrip_a->set_pixel(pStrip_a, 0, color[0], color[1], color[2]);pStrip_a->refresh(pStrip_a, 100);vTaskDelay(500 / portTICK_PERIOD_MS);// Set NeoPixel LED dark by clearing all its individual LEDs.pStrip_a->clear(pStrip_a, 50);vTaskDelay(500 / portTICK_PERIOD_MS);}}}// Task 2.//static void task_blink_led(void * pvParameters) {gpio_reset_pin(GPIO_NUM_46);// Set the GPIO as a push/pull output//gpio_set_direction(GPIO_NUM_46, GPIO_MODE_OUTPUT);// Stay in an endless loop. Don't return from this task.//while (true) {gpio_set_level(GPIO_NUM_46, true);   // LED onvTaskDelay(100 / portTICK_PERIOD_MS);gpio_set_level(GPIO_NUM_46, false);  // LED offvTaskDelay(100 / portTICK_PERIOD_MS);gpio_set_level(GPIO_NUM_46, true);   // LED onvTaskDelay(100 / portTICK_PERIOD_MS);gpio_set_level(GPIO_NUM_46, false);  // LED offvTaskDelay(2700 / portTICK_PERIOD_MS);}}extern "C" void app_main(void) {static const char *TAG = "DUAL_BLINK";int core = xPortGetCoreID();ESP_LOGI(TAG, "app_main running on core %i", core);ESP_LOGI(TAG, "CONFIG_BLINK_GPIO %i", CONFIG_BLINK_GPIO);// Create task 1.//TaskHandle_t xHandle1 = NULL;static uint8_t ucParameterToPass1 = 1;xTaskCreate(task_blink_neo_pixel,"BlinkNeoPixel",// human readable task name.2048,   // stack size in bytes.&ucParameterToPass1,tskIDLE_PRIORITY,&xHandle1);configASSERT(xHandle1);// Create task 2.//TaskHandle_t xHandle2 = NULL;static uint8_t ucParameterToPass2 = 1;xTaskCreate(task_blink_led,"BlinkLED", // human-readable task name.2048,   // stack size in bytes.&ucParameterToPass2,tskIDLE_PRIORITY,&xHandle2);configASSERT(xHandle2);// Stay in an endless loop. Don't return from this task.//while (true) {vTaskDelay(1000 / portTICK_PERIOD_MS);}}

simple programming of the esp32-s3-devkitc-1 using esp-idf, version 2

ESP32-S3-DevKitC-1 with externally wired LED — drawn with KiCad 6

I’ve been cleaning up some code, and I decided to go back and do a smidge of refactoring of the original post. So here’s the new code. Comments on what changed and why follows.

#include <stdio.h>#include "freertos/FreeRTOS.h"#include "freertos/task.h"#include "driver/gpio.h"#include "esp_log.h"#include "led_strip.h"#include "sdkconfig.h"#include <vector>#include <tuple>typedef std::tuple<int, int, int, int> led_color;const std::vector <led_color> colors {{0,32,0,0},  // red{0,0,32,0},  // green{0,0,0,32},  // blue{0,0,32,32}, // cyan{0,32,0,32}, // magenta{0,32,16,0}, // yellow{0,0,0,0}// black};// Task 1.//static void task_blink_neo_pixel(void * pvParameters) {static led_strip_t *pStrip_a;pStrip_a = led_strip_init(CONFIG_BLINK_LED_RMT_CHANNEL, CONFIG_BLINK_GPIO, 1);pStrip_a->clear(pStrip_a, 50);// Stay in an endless loop. Don't return from this task.//while(true) {for(led_color c : colors) {pStrip_a->set_pixel(pStrip_a,std::get<0>(c), std::get<1>(c), std::get<2>(c), std::get<3>(c));pStrip_a->refresh(pStrip_a, 100);vTaskDelay(500 / portTICK_PERIOD_MS);// Set NeoPixel LED dark by clearing all its individual LEDs.pStrip_a->clear(pStrip_a, 50);vTaskDelay(500 / portTICK_PERIOD_MS);}}}// Task 2.//static void task_blink_led(void * pvParameters) {gpio_reset_pin(GPIO_NUM_46);// Set the GPIO as a push/pull output//gpio_set_direction(GPIO_NUM_46, GPIO_MODE_OUTPUT);// Stay in an endless loop. Don't return from this task.//while (true) {gpio_set_level(GPIO_NUM_46, true);   // LED onvTaskDelay(100 / portTICK_PERIOD_MS);gpio_set_level(GPIO_NUM_46, false);  // LED offvTaskDelay(100 / portTICK_PERIOD_MS);gpio_set_level(GPIO_NUM_46, true);   // LED onvTaskDelay(100 / portTICK_PERIOD_MS);gpio_set_level(GPIO_NUM_46, false);  // LED offvTaskDelay(2700 / portTICK_PERIOD_MS);}}extern "C" void app_main(void) {static const char *TAG = "DUAL_BLINK";int core = xPortGetCoreID();ESP_LOGI(TAG, "app_main running on core %i", core);ESP_LOGI(TAG, "CONFIG_BLINK_GPIO %i", CONFIG_BLINK_GPIO);// Create task 1.//TaskHandle_t xHandle1 = NULL;static uint8_t ucParameterToPass1 = 1;xTaskCreate(task_blink_neo_pixel,"BlinkNeoPixel",// human readable task name.2048,   // stack size in bytes.&ucParameterToPass1,tskIDLE_PRIORITY,&xHandle1);configASSERT(xHandle1);// Create task 2.//TaskHandle_t xHandle2 = NULL;static uint8_t ucParameterToPass2 = 1;xTaskCreate(task_blink_led,"BlinkLED", // human-readable task name.2048,   // stack size in bytes.&ucParameterToPass2,tskIDLE_PRIORITY,&xHandle2);configASSERT(xHandle2);// Stay in an endless loop. Don't return from this task.//while (true) {vTaskDelay(1000 / portTICK_PERIOD_MS);}}

Here’s what changed.

  • ESP-IDF was updated to version 4.4.3. I’m using that now, waiting in anticipation of the version 5.0.0 drop. Version 5 is now a release candidate, but I’m holding off stepping up to that until there’s a full formal release.
  • The source file was renamed to dualblink.cpp. That cpp extension signals to the tool chain to treat this as a C++ source file, with all that implies. This allowed me to include vector and tuple (lines 18 and 19), which then provided me with a better way to define the collection of colors to cycle through in the task_blink_neo_pixel task. This eliminates having to explicitly define a size of the data structure.
  • I was able to combine the new way of creating my collection of colors (line 26) with the C++ foreach loop (line 46) and clearly and simply iterate through the collection. You’ll note that I did not have to explicitly declare an iterator nor manipulate it anywhere in the foreach block. My only annoyance is syntax for how to reference each individual tuple element for the call to set the neopixel color (line 48).
  • I had to declare the app_main entry point as extern “C” in order for the linker to properly link with FreeRTOS.

The link to the older post is here: /2022/03/23/simple-programming-of-the-esp32-s3-devkitc-1-using-esp-idf/