C++ nested function definitions

C++ nested function definitions

In a number of languages I've used in the past, there was a direct and simple method of nesting function definitions.
This is useful if, for example, you have a function which needs some "helper" functions which are relevant only to that main function and may be used a few times. It keeps things tidy because these "sub" functions are contained within the definition of the main function but at a separate level.

Of course, in C++ you can pack everything into a single class, but then functions (methods) within that class are all at the same level. If you don't like that, then you can start nesting classes. You can also use macros, if appropriate. But you can also do this, which more closely matches the nesting paradigm of certain high level languages. Simply defines a class, consisting just of static methods embedded within the body of the function. In this case (example below), the method Ipds::cvt() is defined within the function displayIpAddress( ). Of course, there are other ways of doing it and the example itself has a lot of scope for optimisation, but maybe someone finds this useful as an illustration. I've actually only discovered recently that it could be done quite painlessly in C++, hence this post.

// demonstration of nested function definitions

void displayIpAddress( ) {

  // globals:  IPAddress ip, bool inDisplayIpAddress

  class Ipds {
    public:
      static void cvt( uint8_t octet ) {
        digit[ 0 ] = 0 ;
        digit[ 1 ] = 0 ;
        digit[ 2 ] = 0 ;
        digit[ 3 ] = ip[ octet ] / 100 ;
        digit[ 4 ] = ip[ octet ] % 100 / 10 ;
        digit[ 5 ] = ip[ octet ] % 10 ;
      }
  };

  static uint8_t phase = 0 ;
  static uint32_t phaseStartedAtMs  = 0 ;
  const uint32_t phaseInterval = 3000 ; // 3 seconds
  if (  inDisplayIpAddress == false ) return ;


  switch ( phase ) {

    case 0 : {
        phase = 1 ;
        phaseStartedAtMs = millis() ;
        // display IP address part 1
        Ipds::cvt(  0 ) ;
        writeDisplay() ;
        break ;
      }

    case  1 : {
        if ( millis() - phaseStartedAtMs > phaseInterval ) {
          phase = 2 ;
          phaseStartedAtMs = millis() ;
          // display IP address part 2
          Ipds::cvt(  1 ) ;
          writeDisplay() ;
        }
        break ;
      }

    case  2 : {
        if ( millis() - phaseStartedAtMs > phaseInterval ) {
          phase = 3 ;
          phaseStartedAtMs = millis() ;
          // display IP address part 3
          Ipds::cvt(  2 ) ;
          writeDisplay() ;
        }
        break ;
      }

    case  3 : {
        if ( millis() - phaseStartedAtMs > phaseInterval ) {
          // terminate
          inDisplayIpAddress = false ;
        }
        break ;
      }

  }

}

without going to classes, lambdas are useful too. try this

void setup() {
  int data[] = {10, 20, 30, 42};
  auto lambdaFunction { [](int n) -> void { Serial.println(n);}};
  Serial.begin(115200);
  for (auto& v : data) lambdaFunction(v);
}
void loop() {}

Lambda expressions have their own unique syntax which is not highly intuitive. I did study LISP in my student days (many years ago) and used them then but little since. The range based for loop is also (relatively) new in C++. But, yes, lambda expressions are another way of embedding function definitions within functions. I might take the opportunity to practice lambdas again the next time a user enrols onto the forum to get his school homework done :slight_smile:

indeed the syntax (and the capture concept) is somewhat frightening at first sight :slight_smile:
May be it's easier for the eye written like this

int data[] = {10, 20, 30, 42};
void setup() {
  auto lambdaFunction = [](int n) { Serial.println(n);};
  Serial.begin(115200);
  for (int i=0; i< 4; i++) lambdaFunction(data[i]);
}
void loop() {}

Since the lambda does not return anything you can skip the -> void and the definition can also be done as an assignment, so it's a bit easier to read?

6v6gt:
In a number of languages I've used in the past, there was a direct and simple method of nesting function definitions.
This is useful if, for example, you have a function which needs some "helper" functions which are relevant only to that main function and may be used a few times. It keeps things tidy because these "sub" functions are contained within the definition of the main function but at a separate level.

i believe private functions defined within classes serves the role you suggest. there's no need to define them within a function. the public functions have access to the them and they may be useful to more than on public class function.

if the class implementation changes, the public interfaces remain the same and the code using them is unaffected.

Of course, packing everything into a class is, as I also said, another option. However, then, the textual hierarchy is completely flat.
Nesting of function definitions is directly supported in some high level procedural languages so my intention was to present the simplest way I could find of modelling this feature in C++ for those who may be interested. I tend to use name spaces in C++ Arduino developments, and use classes only where multiple instances are required, so there are circumstances where I find a hierarchical structure within a function to be useful. Ultimately, it is a programming style preference, nothing more.

yes - thanks for sharing your thoughts and experiment (Defining a structure would also work).

what is the problem you're trying to solve?

Pascal allows function/procedures defined within function/procedures. presumably the benefit is avoiding redundant code.

Also, PL/1 and a lot scripting languages permit it as well and surely many more.
In the case of a large function which require requires the use of one or more minor functions, which are not used else where in the code, it can make sense to incorporate those minor functions in the body of the main function so they are not cluttering up at the same level as that main function. It is not a problem to be solved, it is simply a matter of style.

at what point does it make sense to create a new file including non-public functions and data structures containing all the code for some "module"?

i thought symbol.c pg 248 in the description of hoc chap-8 of The Unix Programming Environment demonstrated the concept of OOP, a separate file that managed symbols in a simple scripting language.

Well, as I said, it is a matter of style and, as such, there will always be different viewpoints to discuss.

6v6gt:
Well, as I said, it is a matter of style

it's important to have a uniform style if you're working with others who review, work or maintain the code you write.

how would you feel about reusing code from github that uses unusual coding techniques?

gcjr:
it's important to have a uniform style if you're working with others who review, work or maintain the code you write.

how would you feel about reusing code from github that uses unusual coding techniques?

Well, yes of course, but then there is also that age old maxim, which even predates all that stuff that Kernighan and Ritchie wrote, that beggars can't be choosers.

syntactic sugar

JimEli:
syntactic sugar

sp. "syntactic rigour"

occam 2

As you will know from elsewhere* I have just discovered lambda expressions. I find the capture concept odd because the default is that variables that are in scope in the enclosing function are out of scope in the lambda expression. That just seems backward to me, in every other context that I am aware of variables that are in scope in the wider context are are in scope in a smaller part.

*For others reading this here:

I see how this can be confusing but this approach is also consistent with other constructs in C++, where external variables (besides globals) are not implicitly available, ensuring a uniform programming model.

In C++, lambda expressions do not have direct access to enclosing scope variables by default, because it encourages encapsulation and makes them independent entities. This explicit capture mechanism allows control over variable access (prevents unintended modifications / reducing lifetime issues).

The spec requires an explicit capture list and so it becomes clear which external variables a lambda depends on. See that as promoting better code clarity and critical thinking about necessary data.