mercredi 30 juin 2021

Automatically downcast a shared_ptr in parent class to child class type

I have a virtual parent class for collecting reports with its associated report struct. The reports should be rendered as a JSON string in the end, so I'm using https://github.com/nlohmann/json to help me with that.

I create different different child classes to generate such reports of the respective child report structs, and the challenge is that each child report may have slightly different fields, but inherit some from the parent. I have the macros that are needed to convert the structs to JSON representation, defined per report type. This is the code so far:

/**
 * Compile with nlohmann json.hpp
 */

#include <iostream>
#include <vector>
#include <memory>

#include "json.hpp"
using json = nlohmann::json;

struct Report {
  // make abstract
  virtual ~Report() {}
  std::string type = "main_report";
  int foo = 0;
};

struct ChildAReport : public Report {
  std::string type = "child_a_report";
  int bar = 1;
};

struct ChildBReport : public Report {
  std::string type = "child_b_report";
  int baz = 2;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ChildAReport, type, foo, bar)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ChildBReport, type, foo, baz)

class Parent {
protected:
  std::vector<std::shared_ptr<Report>> reports;
  virtual void run() = 0;
  virtual std::string get_report() = 0;
};

class ChildA : public Parent {
public:
  virtual void run() override
  {
    ChildAReport r;
    r.foo = 1;
    r.bar = 2;
    reports.push_back(std::make_shared<ChildAReport>(r));
  }

  std::string get_report() override {
    std::shared_ptr<Report> r = reports.back();
    std::shared_ptr<ChildAReport> cr = std::dynamic_pointer_cast<ChildAReport>(r);
    json r_json = *cr;
    return r_json.dump();
  }
};

class ChildB : public Parent {
public:
  virtual void run() override
  {
    ChildBReport r;
    r.foo = 1;
    r.baz = 3;
    reports.push_back(std::make_shared<ChildBReport>(r));
  }

  std::string get_report() override {
    std::shared_ptr<Report> r = reports.back();
    std::shared_ptr<ChildBReport> cr = std::dynamic_pointer_cast<ChildBReport>(r);
    json r_json = *cr;
    return r_json.dump();
  }
};

int main(int argc, char *argv[])
{
  ChildA ca = ChildA();
  ca.run();
  std::cout << ca.get_report() << std::endl;

  ChildB cb = ChildB();
  cb.run();
  std::cout << cb.get_report() << std::endl;
}

The code is compiled with json.hpp and no further dependencies.

I am expecting this output:

{"bar":2,"foo":1,"type":"child_a_report"}
{"baz":3,"foo":1,"type":"child_b_report"}

Now, in order to actually generate the JSON, I use the get_report() method. I learned that I have to downcast the pointer to the Report struct to the actual ChildA or ChildB struct, because otherwise it won't properly convert to JSON. This is tedious; as you can see, the code is repeated almost verbatim in every possible child class. The run() function is not the problem – here's where all sorts of magic happens, which differs on a per-class basis.

Is there a way I can pull this up to the parent class without having to explicitly specify the type of cast that is to be made before converting to JSON? Ideally this could be inferred depending on the actual type that get_report() is being run on ...

Aucun commentaire:

Enregistrer un commentaire