Still stuck.... vector of structures with std::function and virtual class methods.

Skip to last reply... I still get a few error messages I can't resolve. Anyone able to help?
Thanks!



Two parts... First the explanation then, in the "reply", my attempt at a solution. I would greatly appreciate critique on the code I came up with.

I am continuously working on improving my programming to make it cleaner and easier to maintain. My long term project is a data collection system with *lots* of sensors and *lots* of configurable parameters. In order to update the configuration of the systems in the field, I have a config.ini file which can be sent to the remote units over-the-air or on an SD card. So far so good. Now when I read the config.ini file, I want to do it by stepping through an array which contains the a name of the config.ini section, the key within that section, and the COMMAND (that is, function or object method) which would act on the section.key value from the file, and the function that would be the remedial action in the event the key could not be read.

So, for example: Here is how I do it now, for just one configurable parameter, without an array...

1
2
3
4
5
6
7
8
9
10
        if (ini.getValue("s_4_20_MA_1", "ACTIVE", buffer, bufferLen))
        {
            PrintSectionHas("s_4_20_MA_1", "ACTIVE", buffer);
            S_4_20_MA_1.SetActiveState(bool(std::stoi(buffer))); // 
        }
        else
        {
            PrintCouldNotRead("s_4_20_MA_1", "ACTIVE");
            // Take remedial action here.
        }


as you can see, even with that, the section and key names are used three times each.

Then we have to do this sort of thing over and over with different combinations of sections, keys, and remedial actions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
        ini.getValue("s_4_20_MA_1", "R_PER_D", buffer, bufferLen);
        S_4_20_MA_1.SetReportsPerDay(uint8_t(std::stoi(buffer)));

        ini.getValue("s_4_20_MA_1", "MATH_B4_SEND", buffer, bufferLen);
        S_4_20_MA_1.SetMath((MATH)(std::stoi(buffer)));

        // etc

        ini.getValue("s_4_20_MA_2", "ACTIVE", buffer, bufferLen);
        S_4_20_MA_2.SetActiveState(bool(std::stoi(buffer)));

        ini.getValue("s_4_20_MA_2", "S_BTWN_S", buffer, bufferLen);
        S_4_20_MA_2.SetSecondsBtwnSamples(uint16_t(std::stoi(buffer)));

        ini.getValue("s_4_20_MA_2", "R_PER_D", buffer, bufferLen);
        S_4_20_MA_2.SetReportsPerDay(uint8_t(std::stoi(buffer)));

        // etc...
        ini.getValue("S_PUL_1", "ACTIVE", buffer, bufferLen);
        S_PUL_1.SetActiveState(bool(std::stoi(buffer)));

        ini.getValue("S_PUL_1", "S_BTWN_S", buffer, bufferLen);
        S_PUL_1.SetSecondsBtwnSamples(uint16_t(std::stoi(buffer)));

// etc... etc...
// note how the type of the data passed from the config.ini changes 


So I envision a vector of structures and stepping through that...
and ultimately, a small piece of code which steps through the array...

After sleeping on it *another* night... I came up the code below. PLEASE CRITIQUE
Last edited on
Here is an attempt... using a structure which includes functions using std::function so we can support class methods and then a vector of these structures. It compiles, testing to follow...

Your comments and suggestions are welcome.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using namespace std;
#include <bits/stdc++.h>

// declare structure
struct configLines
{
    String section;
    String key;
    std::function<void(int)> command;
    std::function<void(int)> remedial_command;
};


void loadConfigLines(void)
{

    vector<configLines> v_configLines = {
        {"heartbeat", "COLOR",
         setHeartBeatColor,
         setHeartBeatColorDefault},

        {"s_4_20_MA_1", "ACTIVE",
         S_4_20_MA_1.SetActiveState(bool()),
         S_4_20_MA_1.SetActiveState(bool(INACTIVE))}

    };
}


void processConfigIni(void)
{
    extern vector<configLines> v_configLines;
    const char *filename = "/config.ini";
    const size_t bufferLen = 80;
    char buffer[bufferLen];

// bunch of stuff happens here to check SD card and attach to .ini file on SD

    loadConfigLines();  // populate the vector of structures 
      
    // now step through the configLines
    for (int i = 0; i < v_configLines.size(); i++)
    {
        extern IniFile ini;
        if (ini.getValue(v_configLines[i, 0], v_configLines[i, 1], buffer, bufferLen));  
           // bool IniFile::getValue(const char* section, const char* key, char* buffer, size_t len, float & val) const
        {
            PrintSectionHas(v_configLines[i, 0], v_configLines[i, 1], buffer);
            v_configLines[i, 2];  // void PrintCouldNotRead(String section, String key);
        }
        else
        {
            PrintCouldNotRead(v_configLines[i, 0], v_configLines[i, 1]); // void PrintSectionHas(String section, String key, char *value);
            v_configLines[i, 3] // Take remedial action here.
        }
    }
Last edited on
It's kind of difficult to provide suggestions because it depends on what you want to achieve. But I can show you how (roughly) I would approach it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
namespace ini
{
  struct path_type
  {
    std::vector<std::string> data;

    path_type() = default;
    path_type(const std::string& section, const std::string& key) : data{section, key}
    {
    }

    bool operator==(const path_type& other) const
    {
      return data == other.data;
    }
    bool operator<(const path_type& other) const // Note: this is necessary for sorting e.g. in std::map
    {
      return data < other.data;
    }
  };

  struct file_type
  {
  ...
    bool open(const std::string& fn);
  ...
    std::optional<std::string> getValue(const path_type& path)
    {
      std::optional<std::string> result;
      if(path.data.size() != 2)
        std::cout << "Error ...";
      else
      {
        ...
      }
      return result;
    }

    std::map<path_type, std::string> getValues(const std::set<path_type>& path_set)
    {
      std::map<path_type, std::string> result;

      for(const path_type& path : path_set)
      {
        std::optional<std::string> val = getValue(path);
        if(val.has_value())
          result[path] = *val;
      }
      return result;
    }
  };
}

void processConfigIni(void)
{
    extern vector<configLines> v_configLines;
    const char *filename = "/config.ini";
    const size_t bufferLen = 80;
    char buffer[bufferLen];

// bunch of stuff happens here to check SD card and attach to .ini file on SD

    const ini::path_type paths[] = { {"heartbeat", "COLOR"}, {"s_4_20_MA_1", "ACTIVE"} };

    extern ini::file_type iniFile;
    const auto values = iniFile.getValues(std::set(std::begin(paths), std::end(paths)));

    for (const ini::path_type& path : paths)
    {
      if(values.find(path) == values.end())
      {
        PrintCouldNotRead(values.first.data[0], values.first.data[1]);
      }
    }
    for (const auto& value : values)
    {
        PrintSectionHas(value.first.data[0], value.first.data[1], value.second);
      // e.g. if(value.first == ini::path_type{"heartbeat", "COLOR"})
      // do something with value.second
    }
}
Last edited on
Coder777,
First, and most importantly, thank you.
This code is fascinating to me. It will take me a while to fully grok it and learn from it.
What I am missing, and to me, the hard part, is not so much the strings, but rather the functions/methods, which, as far as I can see are not in the code you generously provided.
So, for example, for each pair of the strings I am searching for with iniFile.getValues, I have to then run one of two functions/methods. It is adding and properly using those functions which eludes me. To be clear, when I find the section and key strings in the .ini file, I run the first function/method with the data associated with the key. If the value is not found, I run the "remedial" function/method with data included in the function as saved in the vector. DOes that explain it any better?
Yes, I'm aware that your final result is to process the data. The best way to do so depends on your needs. Here are some (but certainly not all) ways:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
namespace ini
{
...
  struct process_type
  {
      std::function<void(const std::string&)> command; // Note: the input is supposed to be string. The conversion may happen inside the function
      std::function<void()> remedial_command;

      process_type(const std::function<void(std::string)>& c, const std::function<void()>& r) : command{c}, remedial_command{r}
      {
      }
  };
...
}

struct path_process_type : ini::path_type
{
  ini::process_type proc;

  path_process_type(const std::string& section, const std::string& key, const process_type& p) : ini::path_type{section, key}, proc{p}
  {
  }
};

void setHeartBeatColor(const std::string&);
void setHeartBeatColorDefault();

void processConfigIni(void)
{
  using namespace std::placeholders;    // adds visibility of _1, _2, _3,...

// bunch of stuff happens here to check SD card and attach to .ini file on SD

    const auto lambda1 = [](const std::string& s)
    {
      S_4_20_MA_1.SetActiveState(bool());
    };
    const auto lambda2 = []()
    {
      S_4_20_MA_1.SetActiveState(bool(INACTIVE))
    };

    const path_process_type paths[] = { {"heartbeat", "COLOR", {std::bind (setHeartBeatColor,_1), setHeartBeatColorDefault}}
    , {"s_4_20_MA_1", "ACTIVE", {lambda1, lambda2}} };

    extern ini::file_type iniFile;
    const auto values = iniFile.getValues(std::set(std::begin(paths), std::end(paths)));

    for (const path_process_type& p : paths)
    {
      if(values.find(p) != values.end())
      {
        PrintCouldNotRead(values.first.data[0], values.first.data[1]);
        p.proc.remedial_command();
      }
      else
      {
        PrintSectionHas(value.first.data[0], value.first.data[1], value.second);
        p.proc.command(value.second);
      }
    }
}
For bind() see:

https://cplusplus.com/reference/functional/bind/

For lambda functions see:

https://en.cppreference.com/w/cpp/language/lambda

Note that it is not tested!
Wow Coder777. Thank you. Okay... so a lamda has to be created for each of the class methods and I will use bind for "normal" functions. Got it.

So, no declaration of a std::vector needed? Just build the vector? Vector is implied and understood by the compiler?
 
 const path_process_type paths[] = { {"heartbeat", "COLOR", {std...


The stuff in the namespace ini section and the following struct declaration, I think I get it when I read it but I certainly don't get it to the point where I could write it myself. Any suggestions for learning that part?

Should this line:
 
struct path_process_type : ini::path_type

read
 
struct path_process_type : ini::process_type

? I see no "path_type" as a potential parent.

Thanks again. I will work with this until I grok it.

So, no declaration of a std::vector needed? Just build the vector? Vector is implied and understood by the compiler?

That's a C-style array, not a vector.
Last edited on
a lamda has to be created for each of the class methods and I will use bind for "normal" functions.
Not necessarily. When you have member function you can use bind() as well. It just needs to be the right signature:
1
2
3
4
5
6
7
8
9
struct x
{
  void FromIni(const std::string& s);
};

...
x someObject;
...
const path_process_type paths[] = { {"x", "y", {std::bind (&x::FromIni, &someObject,_1), ...


So, no declaration of a std::vector needed? Just build the vector? Vector is implied and understood by the compiler?
As @MikeyBoy pointed out it is not a std::vector. In this case the additional features are not used.

? I see no "path_type" as a potential parent.
For brevity I omitted path_type in my last post. See the previous. And yes the base must be path_type because iniFile.getValues(...) expects path_type.
coder777,
I had a few personal emergencies which took me away for this for a while but I am back at it and feel I am close to nailing this.

I get some errors that I can't mentally parse when I get down to:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 const path_process_type paths[] =
        {
            {"heartbeat", "COLOR", {std::bind(setHeartBeatColor, _1), setHeartBeatColorDefault}},
            {"s_4_20_MA_1", "ACTIVE", {lambda1, lambda2}}};
    //! no instance of constructor "ini::path_process_type::path_process_type" matches the argument listC/C++(289)
    //! main.cpp(138, 13): argument types are: (const char [10], const char [6], {...})
    //! (const ini::path_process_type)dynamic-init: <constructor-call>

    extern file_type iniFile;
    const auto values = iniFile.getValues(std::set(std::begin(paths), std::end(paths)));
    //! no suitable user-defined conversion from "std::set<ini::path_process_type,
    //! std::less<ini::path_process_type>, std::allocator<ini::path_process_type>>"
    //! to "const std::set<ini::path_type, std::less<ini::path_type>,
    //! std::allocator<ini::path_type>>" existsC/C++(312) 


I also am not groking how to reference section and key out of path when I am trying to use the iniFile getValue to return a result in GetValue.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct file_type
  {
    bool open(const std::string &fn);

    std::optional<std::string> getValue(const path_type &path)
    {
      std::optional<std::string> result;
      if (path.data.size() != 2)
        // std::cout << "Error ...";
        ESP_LOGE("struct file_type", "getValue wrong number of values");
      else
      {
        const size_t bufferLen = 80;
        char buffer[bufferLen];
        //!  HOW DO I SPECIFY section and key BELOW?
        if (configInit.getValue(path.data.section.c_str(), path.data.key.c_str(), buffer, bufferLen))
//! class "std::vector<std::__cxx11::string, 
//! std::allocator<std::__cxx11::string>>" has no member "section"C/C++(135)
        {
          PrintSectionHas(path.data.section, path.data.key, buffer);
          result = buffer;
        }
      }
      return result;
    }


maybe a little help on these points will get me over the hump (again). Thanks.

Here is my complete code (minus some of the utility routines)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#include "includes.h"

#include <vector>
#include <optional>
#include <map>
#include <set>

#include <IniFile.h>

IniFile configInit("/config.ini");

class Sensor
{
private:
  bool m_ActiveState;

public:
  Sensor(bool ActiveState)
  {
    m_ActiveState = ActiveState;
  }

  void SetActiveState(bool activeState)
  {
    m_ActiveState = activeState;
  }

  bool GetActiveState(void)
  {
    return (m_ActiveState);
  }
};

namespace ini
{
  // using namespace std;
  struct path_type
  {
    std::vector<std::string> data;

    path_type() = default;
    path_type(const std::string &section, const std::string &key) : data{section, key}
    {
    }

    bool operator==(const path_type &other) const
    {
      return data == other.data;
    }
    bool operator<(const path_type &other) const // Note: this is necessary for sorting e.g. in std::map
    {
      return data < other.data;
    }
  };

  struct file_type
  {
    bool open(const std::string &fn);

    std::optional<std::string> getValue(const path_type &path)
    {
      std::optional<std::string> result;
      if (path.data.size() != 2)
        // std::cout << "Error ...";
        ESP_LOGE("struct file_type", "getValue wrong number of values");
      else
      {
        const size_t bufferLen = 80;
        char buffer[bufferLen];
        //!  HOW DO I SPECIFY section and key BELOW?
        if (configInit.getValue(path.data.section.c_str(), path.data.key.c_str(), buffer, bufferLen))
        //! class "std::vector<std::__cxx11::string, 
        //! std::allocator<std::__cxx11::string>>" has no member "section"C/C++(135)
        {
          PrintSectionHas(path.data.section, path.data.key, buffer);
          result = buffer;
        }
      }
      return result;
    }

    std::map<path_type, std::string> getValues(const std::set<path_type> &path_set)
    {
      std::map<path_type, std::string> result;

      for (const path_type &path : path_set)
      {
        std::optional<std::string> val = getValue(path);
        if (val.has_value())
          result[path] = *val;
      }
      return result;
    }
  };

  struct process_type
  {
    std::function<void(std::string &)> command; // Note: the input is supposed to be string. The conversion may happen inside the function
    std::function<void()> remedial_command;

    process_type(const std::function<void(std::string)> &c, const std::function<void()> &r) : command{c}, remedial_command{r}
    {
    }
  };

  struct path_process_type : ini::path_type
  {
    process_type proc;

    path_process_type(std::string &section, std::string &key, const ini::process_type &p) : path_type{section, key}, proc{p}
    {
    }
  };

  void setHeartBeatColor(const char *);
  void setHeartBeatColorDefault();

#define ACTIVE true
#define INACTIVE false

  Sensor S_4_20_MA_1(INACTIVE);

  void processConfigIni(void)
  {
    using namespace std::placeholders; // adds visibility of _1, _2, _3,...

    // bunch of stuff happens here to check SD card and attach to .ini file on SD

    const auto lambda1 = [](std::string &s)
    {
      S_4_20_MA_1.SetActiveState(bool());
    };
    const auto lambda2 = []()
    {
      S_4_20_MA_1.SetActiveState(bool(INACTIVE));
    };

    const path_process_type paths[] =
        {
            {"heartbeat", "COLOR", {std::bind(setHeartBeatColor, _1), setHeartBeatColorDefault}},
            {"s_4_20_MA_1", "ACTIVE", {lambda1, lambda2}}
        };
    //! no instance of constructor "ini::path_process_type::path_process_type" matches the argument listC/C++(289)
    //! main.cpp(138, 13): argument types are: (const char [10], const char [6], {...})
    //! (const ini::path_process_type)dynamic-init: <constructor-call>

    extern file_type iniFile;
    const auto values = iniFile.getValues(std::set(std::begin(paths), std::end(paths)));
    //! no suitable user-defined conversion from "std::set<ini::path_process_type,
    //! std::less<ini::path_process_type>, std::allocator<ini::path_process_type>>"
    //! to "const std::set<ini::path_type, std::less<ini::path_type>,
    //! std::allocator<ini::path_type>>" existsC/C++(312)

    for (const ini::path_process_type &p : paths)
    {
      if (values.find(p) != values.end())
      {
        PrintCouldNotRead(values.first.data[0], values.first.data[1]);
        p.proc.remedial_command();
      }
      else
      {
        PrintSectionHas(value.first.data[0], value.first.data[1], value.second);
        p.proc.command(value.second);
      }
    }
  }
} // namespace ini

void setup()
{
}

void loop()
{
}
Last edited on
Sorry for the late reply. Here is the corrected version:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#include "includes.h"

#include <vector>
#include <optional>
#include <map>
#include <set>
#include <string>

#include <IniFile.h>

IniFile configInit("/config.ini");

class Sensor
{
private:
  bool m_ActiveState;

public:
  Sensor(bool ActiveState)
  {
    m_ActiveState = ActiveState;
  }

  void SetActiveState(bool activeState)
  {
    m_ActiveState = activeState;
  }

  bool GetActiveState(void)
  {
    return (m_ActiveState);
  }
};

namespace ini
{
  // using namespace std;
  struct path_type
  {
    std::vector<std::string> data;

    path_type() = default;
    path_type(const std::string &section, const std::string &key) : data{section, key}
    {
    }

    bool operator==(const path_type &other) const
    {
      return data == other.data;
    }
    bool operator<(const path_type &other) const // Note: this is necessary for sorting e.g. in std::map
    {
      return data < other.data;
    }
  };

  struct file_type
  {
    bool open(const std::string &fn);

    std::optional<std::string> getValue(const path_type &path)
    {
      std::optional<std::string> result;
      if (path.data.size() != 2)
       {
        // std::cout << "Error ...";
        //ESP_LOGE("struct file_type", "getValue wrong number of values");
       }
      else
      {
        const size_t bufferLen = 80;
        char buffer[bufferLen];
        //!  HOW DO I SPECIFY section and key BELOW?
        if (configInit.getValue(path.data[0].c_str(), path.data[1].c_str(), buffer, bufferLen)) // Note: data[0] -> section, data[1] -> key
        //! class "std::vector<std::__cxx11::string, 
        //! std::allocator<std::__cxx11::string>>" has no member "section"C/C++(135)
        {
          PrintSectionHas(path.data[0], path.data[1], buffer);
          result = buffer;
        }
      }
      return result;
    }

    std::map<path_type, std::string> getValues(const std::set<path_type> &path_set)
    {
      std::map<path_type, std::string> result;

      for (const path_type &path : path_set)
      {
        std::optional<std::string> val = getValue(path);
        if (val.has_value())
          result[path] = *val;
      }
      return result;
    }
  };

  struct process_type
  {
    std::function<void(const std::string &)> command; // Note: the input is supposed to be string. The conversion may happen inside the function
    std::function<void()> remedial_command;

    process_type(const std::function<void(const std::string&)> &c, const std::function<void()> &r) : command{c}, remedial_command{r}
    {
    }
  };

  struct path_process_type : ini::path_type
  {
    process_type proc;

    path_process_type(const std::string &section, const std::string &key, const ini::process_type &p) : path_type{section, key}, proc{p}
    {
    }
  };

  void setHeartBeatColor(const std::string&); // Note: must be std::string, if you need const char* consider a lambda function
  void setHeartBeatColorDefault();

#define ACTIVE true
#define INACTIVE false

  Sensor S_4_20_MA_1(INACTIVE);

  void processConfigIni(void)
  {
    using namespace std::placeholders; // adds visibility of _1, _2, _3,...

    // bunch of stuff happens here to check SD card and attach to .ini file on SD

    const auto lambda1 = [](const std::string &s)
    {
      S_4_20_MA_1.SetActiveState(bool());
    };
    const auto lambda2 = []()
    {
      S_4_20_MA_1.SetActiveState(bool(INACTIVE));
    };

    path_process_type paths[] =
        {
            {"heartbeat", "COLOR", {std::bind(setHeartBeatColor, _1), setHeartBeatColorDefault}},
            {"s_4_20_MA_1", "ACTIVE", {lambda1, lambda2}}
        };
    //! no instance of constructor "ini::path_process_type::path_process_type" matches the argument listC/C++(289)
    //! main.cpp(138, 13): argument types are: (const char [10], const char [6], {...})
    //! (const ini::path_process_type)dynamic-init: <constructor-call>

    extern file_type iniFile;
    const auto values = iniFile.getValues(std::set<path_type>(std::begin(paths), std::end(paths)));
    //! no suitable user-defined conversion from "std::set<ini::path_process_type,
    //! std::less<ini::path_process_type>, std::allocator<ini::path_process_type>>"
    //! to "const std::set<ini::path_type, std::less<ini::path_type>,
    //! std::allocator<ini::path_type>>" existsC/C++(312)

    for (ini::path_process_type &p : paths)
    {
      const auto it = values.find(p);
      if (it != values.end())
      {
        PrintCouldNotRead(it->first.data[0], it->first.data[1]);
        p.proc.remedial_command();
      }
      else
      {
        PrintSectionHas(it->first.data[0], it->first.data[1], it->second);
        p.proc.command(it->second);
      }
    }
  }
} // namespace ini

void setup()
{
}

void loop()
{
}
Note that const std::string& != std::string&. The const is important.
coder777,
Thank you vary much. I was away for several days and just got to study this and compile it. I only had to change the PrintCountNotRead() and PrintSectionHas() routines to agree on types.

This is elegant code and gets exactly to what I was trying to do. There is so much "devil in the details" of this code that I'm not sure how I will ever learn to write such code from scratch myself. If you have any advice on learning tools, references, etc. that could help me continue to progress my skills to eventually get to this level, I am eager to study and learn.

Thanks again,
Pete
I guess there is real shortcut for experience. One way to improve your style might be the rubber duck debugging:

https://en.wikipedia.org/wiki/Rubber_duck_debugging

Or: how would you explain to someone else what you are doing. If you can't do this in simple words you are most likely about to do something wrong.

Also: short and descriptive names are important.
Topic archived. No new replies allowed.