typedef function pointer array declaration problem
Try replacing

taskDefinition _tasks [ ] [4] = {


by

taskDefinition _tasks [ ] = {


In short, you have defined _tasks as a 2D array of objects (an array
of arrays of objects) while you intend to use it as a 1D array.



Note that the error message is quite explicit if you take the time to
parse it:


  request for member 'task' in '_tasks[((int)i)]',


You are requesting _tasks[i].task,


  which is of non-class type


but _tasks[i] is not an object.


  type 'taskDefinition [4]'


It is instead an array of four taskDefinition objects.



Edit: Fixing the answer to match the latest version of the question.

Your code works, but not quite as you expect it to work. You wrote:

complexTaskDefinition tasks [ ] [4] = ...;


Here you are defining a 2D array of objects, while your intention was to
define a one-dimensional array (why do I feel like I am repeating
myself?).

complexTaskDefinition tasks [ ] [4] = {
    {taskC1, true, -1, 1000 },
    {taskC2, true, 10, 2000 },
    {taskC3, false, 3, 3000 },
};


This definition, although syntactically correct, is awfully wacky. You
are defining tasks as an array of 3 items. Each item is itself an
array of 4 objects, where the first object of the array is explicitly
initialized, and the three others are implicitly initialized to
all-bits-zero. So basically your definition is equivalent to this
explicit version:

complexTaskDefinition tasks [3] [4] = {
    {{taskC1, true, -1, 1000 }, {nullptr, false, 0, 0},
     {nullptr, false, 0, 0},    {nullptr, false, 0, 0}},
    {{taskC2, true, 10, 2000 }, {nullptr, false, 0, 0},
     {nullptr, false, 0, 0},    {nullptr, false, 0, 0}},
    {{taskC3, false, 3, 3000 }, {nullptr, false, 0, 0},
     {nullptr, false, 0, 0},    {nullptr, false, 0, 0}}
};


Then you wrote:

task = tasks [ i ];


Here, tasks[i] is a row of the 2D array, i.e. a 1D array of 4 objects,
whereas task is a pointer to object. The assignment is legit because
here, like in most contexts, the array “decays” to a pointer to its
first element. The above line is equivalent to the explicit version:

task = &amp;tasks[i][0];


Quite fortunately, this happens to point to one of the elements of the
array that has been explicitly initialized!

As an illustration of the above, here is a modified version of
showComplexTaskList() that loops over all the tasks in the array:

void showComplexTaskList () {
    Serial.println ( F ( "\nComplex Task List:" ) );
    Serial.print(F("number of tasks: "));
    Serial.println(sizeof tasks / sizeof(complexTaskDefinition));
    for (uint8_t i = 0; i &lt; TASK_COUNT; i++) {
        for (uint8_t j = 0; j &lt; 4; j++) {
            task = &amp;tasks[i][j];
            Serial.print ( '[' );
            Serial.print ( i );
            Serial.print ( F ("]["));
            Serial.print ( j );
            Serial.print ( F ( "] REPEAT:" ));
            Serial.print ( ( task-&gt;repeat ) ? "YES" : "NO" );
            Serial.print ( F ( ", DELAY:" ) );
            Serial.print ( task-&gt;delay );
            Serial.print ( F ( ", ITERATIONS:" ) );
            Serial.print ( task-&gt;iterations );
            Serial.println ();
        }
    }
}


Its output is:

Complex Task List:
number of tasks: 12
[0][0] REPEAT:YES, DELAY:1000, ITERATIONS:-1
[0][1] REPEAT:NO, DELAY:0, ITERATIONS:0
[0][2] REPEAT:NO, DELAY:0, ITERATIONS:0
[0][3] REPEAT:NO, DELAY:0, ITERATIONS:0
[1][0] REPEAT:YES, DELAY:2000, ITERATIONS:10
[1][1] REPEAT:NO, DELAY:0, ITERATIONS:0
[1][2] REPEAT:NO, DELAY:0, ITERATIONS:0
[1][3] REPEAT:NO, DELAY:0, ITERATIONS:0
[2][0] REPEAT:NO, DELAY:3000, ITERATIONS:3
[2][1] REPEAT:NO, DELAY:0, ITERATIONS:0
[2][2] REPEAT:NO, DELAY:0, ITERATIONS:0
[2][3] REPEAT:NO, DELAY:0, ITERATIONS:0


So your program works almost by chance: an error in the way you define
the array cancels an error in the way you use it. This kind of cancelled
error method is very bad programming practice. You are wasting memory in
unused complexTaskDefinition objects. More importantly, your code
doesn't express your intent, which makes the program way harder to
understand and to debug.

The fix is to declare a 1D array, as this has always been the intention:

complexTaskDefinition tasks [ ] = {
    {taskC1, true, -1, 1000 },
    {taskC2, true, 10, 2000 },
    {taskC3, false, 3, 3000 },
};


Now, you can check that this array does only have three tasks, as
reported by sizeof tasks / sizeof(complexTaskDefinition). To use the
array, you can use a pointer if you want:

task = &amp;tasks[i];
// ...
Serial.print ( ( task-&gt;repeat ) ? "YES" : "NO" );


Or you can directly request a member of tasks[i], as in

void showComplexVersion () {
    Serial.println ( F ( "\nComplex Version:" ) );
    for (uint8_t i = 0; i &lt; TASK_COUNT; i++) {
        tasks[i].task();
    }
}


Now I have a question for you. Since I already explained your 1D vs. 2D
array mistake in the first iteration of my answer, why did you persist
with this in your edited question? If you are going to blatantly ignore
the answers you get, at least say so in the question. This could save
time to those who might consider helping you.