Write Less Code using the "auto" Keyword

published: July 12, 2019 — last modified: October 23, 2025

The auto keyword was introduced with C++11. It reduces the amount of code you have to write, reduces repetitive code and the number of required changes. Sadly, many C++ developers are not aware of how useful this keyword is. Especially embedded code can gain a lot by its usage.

In this article, I try to explain a number of useful cases of the auto keyword, using Arduino example code. The same principles are valid for any embedded environment which is using a modern C++ compiler. The C++11 standard is eight years old, and meanwhile, every C++ compiler should support it.

The examples are written for Arduino or Adafruit SAMD boards using the Arduino IDE. The toolchain for these boards use a compiler which supports more features explained in this article. Using auto with AVR code is possible too, just give it a try.

The “auto” Keyword

The auto keyword was introduced to instruct the compiler to automatically deduce a type from the given context. In programming language terms, they call this a placeholder type specifier.

C++11 mainly introduced the auto keyword for variable declarations. These are the main focus of this article.

Simple Variable Declarations

At any point in your code, where you declare a variable which is initialized, you can use auto instead of a type.

auto variable = <some expression>

The following example uses auto in various places:

const auto cFrameSize = 100;
auto gMiddleValue = 0;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void waitUntil(uint32_t timePoint) {
  while (static_cast<int32_t>(timePoint - millis()) > 0) {}  
}

void loop() {
  const auto currentTime = millis();
  const auto middlePoint = currentTime + (cFrameSize - gMiddleValue);
  const auto framePoint = currentTime + cFrameSize;
  digitalWrite(LED_BUILTIN, HIGH);
  waitUntil(middlePoint);
  digitalWrite(LED_BUILTIN, LOW);
  waitUntil(framePoint);
  gMiddleValue += 1;
  if (gMiddleValue >= cFrameSize) {
    gMiddleValue = 0;
  }
}

Variables from Literal Values

The example starts with two variable declarations which are initialized from integer literals:

const auto cFrameSize = 100;
auto gMiddleValue = 0;

The first line is also adding const to create a constant while the second line declares a regular global variable.

Using auto with integer values as shown is not very useful and just used as a demonstration. Especially most people will now know the actual type generated (int). It starts to make sense if you are working with a class, a struct and especially template classes.

If you read my previous article, there I declare a new template class Duration and user-defined literals for time values. To use a millisecond constant, I would have to write:

#include "Duration.hpp"

const Millisecond cSerialTimeout = 100_ms;

If I change the value from milliseconds to microseconds, I also have to change the variable type:

#include "Duration.hpp"

const Microseconds cSerialTimeout = 100_us;

If I use auto as a variable type, no change is needed. I especially do not need to remember the correct type for _ms or _us literals:

#include "Duration.hpp"

const auto cSerialTimeout = 100_us;

Variables for Immediate Results

In the example, I also use auto to create variables for immediate calculation results:

const auto currentTime = millis();
const auto middlePoint = currentTime + (cFrameSize - gMiddleValue);
const auto framePoint = currentTime + cFrameSize;

The const keyword is added to create a constant variable instead of a regular one.

Using auto for immediate variables makes the code shorter and more flexible. If the real-time counter type would change from uint32_t to uint16_t, you do not have to change this code at all.

Simplify Range Based For Loops

The auto keyword is great in range based for loops:

const char* magicLines[] = {
  "Twinkle, twinkle, little star,",
  "How I wonder what you are!",
  "Up above the world so high,",
  "Like a diamond in the sky.",
  "When this blazing sun is gone,",
  "When he nothing shines upon,",
  "Then you show your little light,",
  "Twinkle, twinkle, through the night.",
};

void setup() {
  Serial.begin(115200);
  while (!Serial) {};
  for (auto line : magicLines) {
    Serial.println(line);
  }
}

void loop() {
}

If you are using complex types, it makes sense to use a const reference in the for loop as shown in the next example:

struct MagicLine {
  const char *text;
  uint32_t delay;
};

const MagicLine magicLines[] = {
  {"Twinkle, twinkle, little star,", 800},
  {"How I wonder what you are!", 1000},
  {"Up above the world so high,", 1200},
  {"Like a diamond in the sky.", 2000},
  {"When this blazing sun is gone,", 500},
  {"When he nothing shines upon,", 800},
  {"Then you show your little light,", 1000},
  {"Twinkle, twinkle, through the night.", 500},
};

void setup() {
  Serial.begin(115200);
  while (!Serial) {};
  for (const auto &line : magicLines) {
    Serial.println(line.text);
    delay(line.delay);
  }
}

void loop() {
}

Make Register Access Configurable

In embedded software, you often have to deal with the low-level hardware API for performance reasons. This usually includes direct register access.

The hardware API of the manufacturer of most microcontrollers is an unfathomable mess of macros and nested structures. It can take a very long time to find the correct type names in these files.

Instead of doing all this research, you can simply use an auto reference to the registers or structures. You can give this reference a short, easy to read name. As shown in the following example:

// For Adafruit Feather M0

constexpr auto &port = PORT->Group[0];
constexpr auto &portDir = port.DIR.reg;
constexpr auto &portOut = port.OUTTGL.reg;

const auto mask = 0b100000000000000000u;

void setup() {
  portDir |= mask;
}

void loop() {
  portOut = mask;
  delay(200);
}

The variables port, portDir and portOut create a simple reference to the registers and/or structures which can be easily used in the following code. Changing the port can be quickly done in the first line, without touching the rest of the code.

I use constexpr in front of these variables to make sure the compiler is using the original location of the reference and does not implement the variable as a pointer to these registers.

Inline Functions made Simple

Another interesting use of the auto keyword is to declare a variable for an labda function quickly. In some cases, you need a very small function which only makes sense in the context of another function. Many languages support inline functions for this situation.

In C++ you can use lambda functions in this situation, they are declared as shown in the following example:

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  auto flashLed = [](uint32_t duration) {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(duration);
    digitalWrite(LED_BUILTIN, LOW);
    delay(100);
  };
  flashLed(100);
  flashLed(200);
  flashLed(300);
}

The example above could be easily written as shown in the next example:

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void flashLed(uint32_t duration) {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(duration);
  digitalWrite(LED_BUILTIN, LOW);
  delay(100);
}

void loop() {
  flashLed(100);
  flashLed(200);
  flashLed(300);
}

Inline functions get interesting if you access local function variables you otherwise had to pass as parameters. This is the case in the following abstract example:

void addStep(int step, int maximum, int &counterA, int &counterB, int &counterC) {
  counterA += step;
  counterB += step;
  counterC += step;
  if (counterA > maximum) {
    counterA = maximum;
  }
}

void increaser(int maximum) {
  int counterA = 0;
  int counterB = 2;
  int counterC = 10;
  addStep(6, maximum, counterA, counterB, counterC);
  addStep(20, maximum, counterA, counterB, counterC);
  addStep(12, maximum, counterA, counterB, counterC);
  addStep(20, maximum, counterA, counterB, counterC);
}

The example above can be written like this:

void increaser(int maximum) {
  int counterA = 0;
  int counterB = 2;
  int counterC = 10;
  auto addStep = [&](int step) {
    counterA += step;
    counterB += step;
    counterC += step;
    if (counterA > maximum) {
      counterA = maximum;
    }    
  };
  addStep(6);
  addStep(20);
  addStep(12);
  addStep(20);
}

The second implementation is shorter with less repetitition. It also removes the function addStep from the global namespace, which is only used by the increaser function.

Learn More

Conclusion

The auto keyword can make your code shorter and reduces repetitive parts. If used with care, it improves the overall code quality and even simplifies refactoring. Give it a try!

If you have questions, miss some information or have any feedback, feel free to add a comment below.

 

 

 

 

More Posts

The 2024 Update of the Storage Boxes System

Posted on 2024-07-21— 3D Printing, Projects

I'm pleased to share the 2024 revision D of my 3D-printed storage boxes. After extensive testing and fixes—from rail-grid grooves to label-area ribs—I updated geometries, added dividers and 3MF/STEP files, and released hundreds of free models. Read the full post for details and download links.

Read this post

Snowflake Configuration

Posted on 2019-11-22— Projects

I walk you through the Snowflake v1.2 firmware's button-based configuration: short vs long presses, auto and single modes, LED indicators, and pattern durations. I explain the six-second timeout and that settings aren't saved after power loss—please read the full post for step-by-step usage and tips.

Read this post

19" Rack Fan Controller [1/2]

Posted on 2021-03-14— 3D Printing, Build

I migrated several servers into a compact 19-inch rack and reworked my Arduino Uno fan controller to fit a 1U RND enclosure. I show choosing the case, adding a rotary encoder and filter circuits, testing signals, and 3D-printing panels. Read the full post for STL files and build notes.

Read this post

How to Automate the Turn-On Time of a Deco Light

Posted on 2018-12-21— How and Why, Learn

I wanted my winter decoration to switch on at dusk without constant adjustments, so I built a simple low-voltage controller using a P-channel MOSFET, a microcontroller and an ambient light sensor. I walk through my design choices, sealing for outdoor use and programming tips—read on if you'd like to build one too.

Read this post

Organizer Boxes Optimised for 3D-Print

Posted on 2020-11-21— 3D Printing, Common, Projects

I've been reorganising my lab and designed stackable organiser boxes optimised for fast 3D printing. They use a 60×60mm raster, stabilising walls, dual label slots and rail grids. I offer a free STL subset and explain print settings—read the full post for downloads and the complete shop set.

Read this post

Snowflake Decoration Available on the Pimoroni Store

Posted on 2019-11-13— Common

I'm happy to share you can now buy an assembled Snowflake Decoration from Pimoroni with worldwide shipping. The panels arrive pre-soldered, use 5V power and flat cables so no soldering is needed. If you'd like details on the updates and compatibility, please read the full post.

Read this post