Constructor parameters

Constructor parameters

What do we mean constructor parameters? Surely, they are exactly what the title says, what else is there to discuss? You need one or two things? Well, add or or two constructor parameters. Ten things? Ten constructor parameters for you. Now, one or two constructor parameters are definitely OK; ten feels a little excessive, especially if a lot of them share the same type. And just before we jump into things, let’s use ▪▪▪ for custom code placeholder to and … to really mean three dots that are part of C++ code.

class UWidget {
public:
  UWidget(int X, int Y, int W, int H, 
          bool IsModal, bool IsBackAction,
          const FString& Title, const FString& Body, 
          const FArray<▪▪▪> &Buttons) { ▪▪▪ }
};

The usage is pretty straight-forward, and as you write the code, there is little place for confusion. You can point out that you can collapse the (X, Y); (W, H) pairs into their structures; you can start with FPair<int, int>, for example. This helps, but things get a little less convenient when you want to combine this with default values.

class UWidget {
public:
  UWidget(int X, int Y, int W=300, int H=200, 
          bool IsModal=false, bool IsBackAction=true,
          const FString& Title="", const FString& Body="", 
          const FArray<▪▪▪> &Buttons={}) { ▪▪▪ }
};

It quickly becomes inconvenient to define defaults like this; what if you wanted to have the defaults for the X, Y, W, H, IsModal, and IsBackAction; but wanted to change Title to be something else? We can use very specific type wrappers and a “implementation builder pattern” to help us.

struct FPosition {
  int X, Y;
};

struct FSize {
  int W, H;
};

struct FNavigation {
  bool IsModal, IsBackAction;
};

struct FBody {
  FString Title, Body;
};

struct FButtons {
  TArray<▪▪▪> Buttons;
};

class UWidget {
public:
  UWidget(const FPosition &Position, const FSize &Size, 
          const FNavigation &Navigation,
          const FBody &Body, 
          const FButtons &Buttons) { ▪▪▪ }
};

This?! This is not helping anything; and it’s more typing. And where are the promised neat defaults? It seems that we need to add a few more things to bring things together.

class UWidget {
private:
  FPosition Position = {▪▪▪}; 
  FSize Size = {▪▪▪};
  FBody Body = {▪▪▪};
  FButtons Buttons = {▪▪▪};
public:
  UWidget() { ▪▪▪ }

  UWidget &Set(const FPosition &Position) { ▪▪▪; return *this; }
  UWidget &Set(const FSize &Size) { ▪▪▪; return *this; }
  UWidget &Set(const FBody &Body) { ▪▪▪; return *this; }
  UWidget &Set(const FButtons &Buttons) { ▪▪▪; return *this; }
};

OK, we have a fluent interface; but this does not quite match C++. This would be a good fit for Java, but we are in C++; we’d like to write something like UWidget X(FPosition(▪▪▪), FBody(▪▪▪)); specifying only the things we want, but in a constructor call.

class UWidget {
private:
  FPosition Position = {▪▪▪}; 
  FSize Size = {▪▪▪};
  FBody Body = {▪▪▪};
  FButtons Buttons = {▪▪▪};
public:
  template<typename... Args>
  UWidget(Args&&... args) {
    (Set(std::move(args)), ...);
  }

  UWidget &Set(const FPosition &Position) { ▪▪▪; return *this; }
  UWidget &Set(const FSize &Size) { ▪▪▪; return *this; }
  UWidget &Set(const FBody &Body) { ▪▪▪; return *this; }
  UWidget &Set(const FButtons &Buttons) { ▪▪▪; return *this; }
};

All we need is a variadic template and we’re done; now we use the constructor call to override any defaults, and we of course don’t have to worry about the order of their calls. In the constructor body, after the Set call expansion, we can add our normal constructor code. When we call the constructor, we are free to pick order of the parameters and leave out the ones we don’t need.

UWidget A{ FPosition{}, FSize{}, FButtons{}, FBody{} };
UWidget B{ FButtons{}, FSize{}, FPosition{}, FBody{} };
▪▪▪
UWidget C{ FButtons{}, FBody{} };
UWidget D{ FButtons{} };

And before you complain; yes, by the time you add -O3 optimisation parameter, the generated machine code is not too different. As a comment, if this is an instance that is in a hot path, and that gets created frequently, you should still favour performance over readability; in almost all other circumstances, readability and flexibility always wins.

Leave a Reply

Your email address will not be published. Required fields are marked *