Say a class Foo has two dependencies (Bar and Baz), and that it is an error to construct a Foo without providing both of them. Constructor injection makes it easy to guarantee at compile time that this is done:
class Foo
{
public:
Foo(const std::shared_ptr<Bar>& bar, const std::shared_ptr<Baz>& baz);
// (don't get hung up on the type of pointer used; it's for example only)
};
But let's say Foo also needs two doubles:
class Foo
{
public:
Foo(const std::shared_ptr<Bar>& bar, const std::shared_ptr<Baz>& baz,
double val1, double val2);
};
Now there is a problem; it would be really easy for the caller to accidentally transpose val1 and val2 and create a runtime bug. We can add a Params struct to allow named initialization and preclude this:
class Foo
{
public:
struct Params
{
std::shared_ptr<Bar> bar;
std::shared_ptr<Baz> baz;
double val1;
double val2
};
Foo(const Params& params);
};
// ...
std::shared_ptr<Foo> MakeDefaultFoo()
{
Foo::Params p;
p.bar = std::make_shared<Bar>();
p.baz = std::make_shared<Baz>();
p.val1 = 4.0;
p.val2 = 3.0;
return std::make_shared<Foo>(p);
}
But now we have the problem that the caller might forget to populate one of the fields in Params, which would not be detectable until runtime. struct initialization syntax or an initializer list would make it impossible to forget a field, but then we're back to relying on position!
Is there some trick that makes it possible to have the best of both worlds--compiler-enforced mandatory arguments that are assigned by name instead of position?
Just have a simple wrapper may work:
Now types are explicit and you cannot swap them without getting compiler error.