Search code examples
c++c++11arduinovariadic-templatesmicrocontroller

Template parameter pack peel args in pairs


I want to create a function for an ESP2866 microcontroller that saves an arbitrary number of configurations to a config file on the filesystem. I found a way to do it and I was wondering if it could be any better.

// Saves the configuration values to the file system
template <typename... Args>
void SaveConfig(const char *name, String &value, Args &...args)
{
    Serial.println("Saving config...");
    StaticJsonDocument<JSON_OBJECT_SIZE(2) + 200> doc;
    SetData(doc, name, value, args...);

    File configFile = SPIFFS.open("/config.json", "w");
    if (!configFile)
        Serial.println("Failed to open config file for writing!");

    serializeJson(doc, configFile);
    serializeJson(doc, Serial);
    configFile.close();
}

All i would need to do is:

doc[name] = value;

for each pair of arguments in the parameter pack. My solution was that i created a new function SetData() that calls itself with the parameter pack arguments, peeling off two parameters each iteration:

template <typename... Args>
static void SetData(JsonDocument &doc, const char *name, String &value, Args &...args)
{
    doc[name] = value;
    SetData(doc, args...);
}

But this creates another problem. When the parameter pack "runs out" it wants to call SetData() with no parameters. So now I have to create an overload of this function with no parameters (except doc).

So is there a better way of doing this?


Solution

  • If you really want to use templates instead of containers, you can try the following:

    template<typename ...Args, std::size_t ...I>
    void SetDataImpl(JsonDocument& doc, std::tuple<Args...> tup, std::index_sequence<I...>) {
      int dummy[] = {
        (doc[std::get<2*I>(tup)] = std::get<2*I+1>(tup), 0)...
      };
    }
    
    template<typename ...Args>
    void SetData(JsonDocument& doc, Args &...args) {
      static_assert(sizeof...(args) % 2 == 0, "");
      SetDataImpl(doc, std::forward_as_tuple(args...), std::make_index_sequence<sizeof...(args) / 2>{});
    }
    

    But as @HolyBlackCat mentioned, this way would be better.

    void SetData(JsonDocument& doc, std::initializer_list<std::pair<const char*, String>> il = {}) {
      for(const auto& elem : il) { // Just auto& maybe. Depends on json library implementation
        doc[elem.first] = elem.second;
      }
    }