Is it possible to achieve (at least something similar to) this? I need Designated Initializers for "named arguments" and/or possibility to skip setting of some params (not shown here). And still get this "cascade" default values.
Ideally I need set params of Derived (when instantiate) without knowledge of inheritance (because there should be lets say 5 level of inheritance and its user unfriendly have to know how many inheritances there is... ) Of course knowledge of params name and order is needed.
#include <iostream>
using namespace std;
struct Base
{
string baseDefault = "Default";
string label = "Base";
};
struct Derived : public Base
{
// using Base::baseDefault; // do not help
using Base::Base;
string label = "Derived";
};
int main()
{
Derived d1{.baseDefault="ChangedDefault", .label="NewDerived"};
Derived d2{};
Base b1{.label="NewBase"};
Base b2{};
cout << " d1: " << d1.label << d1.baseDefault
<< ", d2: " << d2.label << d2.baseDefault
<< ", b1: " << b1.label << b1.baseDefault
<< ", b2: " << b2.label << b2.baseDefault << endl;
/* expect: d1: NewDerived ChangedDefault,
d2: Derived Default,
b1: NewBase Default,
b2: Base Default
*/
return 0;
}
I try to clarify it:
If no default values needed (or just one for each member), I can do this:
struct Base
{
string withDefault = "baseDefault";
string noDefault;
};
struct Derived : public Base
{
string inDerived; /* no matter with/without default*/
};
int main()
{
Derived d{{.noDefault="SomeSetting"}, .inDerived="NextSetting"};
Base b{.nodefault="SomeSetting"};
return 0;
}
But the problem is: I need to use different default value for /withDefault/ if I constructed /Derived/. So Something like this:
struct Derived : public Base
{
string withDefault = "useThisAsDefaultHere";
string inDerived; /* no matter with/without default*/
};
Consider the following, "naive", design:
#include <iostream>
struct Base {
char const* Base_var1 = "Base_var1";
char const* Base_var2;
};
struct Derived1 : public Base {
char const* Base_var1 = "Derived1_var1";
char const* derived1_var3 = "Derived1_var3";
};
struct Derived2 : public Derived1 {
char const* derived2_var4 = "Derived2_var4";
char const* derived2_var5 = "Derived2_var5";
};
Here we have five variables, ending on _var1
, _var2
, _var3
, _var4
and _var5
respectively. Their prefix is the name of the class that they are first defined in. For example, Base
defines Base_var1
. Although Derived1
overrides the default, it still has the same name in Derived1
of course.
We can thus state that _var1
has a default in Base
that is overridden in Derived1
. _var2
has no default, _var3-5
are introduced, with defaults, in Derived1
and Derived2
respectively.
If now we want to construct an object of type Derived2
where we want to use all default values, except for _var3
and _var5
(and of course give _var2
a value) then we could attempt to do this as follows:
int main()
{
Base b = { .Base_var2 = "main_var2" };
Derived1 d1 = { b, .derived1_var3 = "main_var3" };
Derived2 d2 = { d1, .derived2_var5 = "main_var5" };
std::cout <<
d2.Base_var1 << ", " <<
d2.Base_var2 << ", " <<
d2.derived1_var3 << ", " <<
d2.derived2_var4 << ", " <<
d2.derived2_var5 << std::endl;
}
This has several flaws. The most important one is that it isn't correct C++. When we try to compile this with clang++ we get:
>clang++ -std=c++20 troep.cc
troep.cc:21:22: warning: mixture of designated and non-designated initializers in the same initializer list is a C99 extension [-Wc99-designator]
Derived1 d1 = { b, .derived1_var3 = "main_var3" };
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
troep.cc:21:19: note: first non-designated initializer is here
Derived1 d1 = { b, .derived1_var3 = "main_var3" };
^
troep.cc:22:23: warning: mixture of designated and non-designated initializers in the same initializer list is a C99 extension [-Wc99-designator]
Derived2 d2 = { d1, .derived2_var5 = "main_var5" };
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
troep.cc:22:19: note: first non-designated initializer is here
Derived2 d2 = { d1, .derived2_var5 = "main_var5" };
^~
2 warnings generated.
And the output of the program is:
Derived1_var1, main_var2, main_var3, Derived2_var4, main_var5
which is the desired result.
With clang++ you only get a warning, but with g++ for example, it won't compile and you get the errors:
>g++ -std=c++20 troep.cc
troep.cc: In function ‘int main()’:
troep.cc:21:22: error: either all initializer clauses should be designated or none of them should be
21 | Derived1 d1 = { b, .derived1_var3 = "main_var3" };
| ^
troep.cc:22:23: error: either all initializer clauses should be designated or none of them should be
22 | Derived2 d2 = { d1, .derived2_var5 = "main_var5" };
| ^
The second problem is that Derived1
isn't really overriding the value of Base::Base_var1
but instead is hiding it. If it would be passed to a function that takes a Base&
then Base_var1
would have an "unexpected" value.
The way I solved the first problem is by splitting the structs into *Ext
(extension) structs that define the variables and (initial) defaults, and use classes without member variables for the inheritance. This way you don't run into the need to use a mixture of designated and non-designated:
struct Base {
char const* Base_var1 = "Base_var1";
char const* Base_var2;
};
struct Derived1Ext {
char const* derived1_var3 = "Derived1_var3";
};
struct Derived2Ext {
char const* derived2_var4 = "Derived2_var4";
char const* derived2_var5 = "Derived2_var5";
};
class Derived1 : public Base, public Derived1Ext {
};
class Derived2 : public Base, public Derived1Ext, public Derived2Ext {
};
Initialization of Derived2
then becomes:
Derived2 d2 = {
{ .Base_var2 = "main_var2" },
{ .derived1_var3 = "main_var3" },
{ .derived2_var5 = "main_var5" }
};
which has extra braces, but is over all rather clean - and you still only have to specify values that you want to differ from the defaults.
Of course, this still lacks the override of the default of Base_var1
.
But we can compile this without errors or warnings ;). The output is then
Base_var1, main_var2, main_var3, Derived2_var4, main_var5
The last step is to fix the first value here, without changing main()
again.
The only way I could think of is by using magic values... In the above case we only have char const*
but in general this could become rather elaborate. Nevertheless here is something that does the trick:
#include <iostream>
#include <cassert>
char const* const use_default = reinterpret_cast<char const*>(0x8); // Magic value.
struct BaseExt {
char const* Base_var1 = use_default;
char const* Base_var2 = use_default;
};
struct Base : BaseExt {
void apply_defaults()
{
if (Base_var1 == use_default)
Base_var1 = "Base_var1";
// Always initialize Base_var2 yourself.
assert(Base_var2 != use_default);
}
};
struct Derived1Ext {
char const* derived1_var3 = use_default;
};
struct Derived1 : BaseExt, Derived1Ext {
void apply_defaults()
{
if (Base_var1 == use_default)
Base_var1 = "Derived1_var1"; // Override default of Base!
if (derived1_var3 == use_default)
derived1_var3 = "Derived1_var3";
BaseExt* self = this;
static_cast<Base*>(self)->apply_defaults();
}
};
struct Derived2Ext {
char const* derived2_var4 = use_default;
char const* derived2_var5 = use_default;
};
struct Derived2 : BaseExt, Derived1Ext, Derived2Ext {
void apply_defaults()
{
if (derived2_var4 == use_default)
derived2_var4 = "Derived2_var4";
if (derived2_var5 == use_default)
derived2_var5 = "Derived2_var5";
BaseExt* self = this;
static_cast<Derived1*>(self)->apply_defaults();
}
};
int main()
{
Derived2 d2 = {
{ .Base_var2 = "main_var2"},
{ .derived1_var3 = "main_var3" },
{ .derived2_var5 = "main_var5" },
};
d2.apply_defaults();
std::cout <<
d2.Base_var1 << ", " <<
d2.Base_var2 << ", " <<
d2.derived1_var3 << ", " <<
d2.derived2_var4 << ", " <<
d2.derived2_var5 << std::endl;
}
EDIT: Improved version
Instead of the use_default
magic number you can use std::optional
. And instead of having to call apply_defaults
manually, you can add an other member (dummy
) that does this for you. By adding the [[no_unique_address]]
this addition is (probably) completely optimized away (the size of the struct does not increase (the use of std::optional
DOES make the struct increase, of course)).
#include <iostream>
#include <optional>
#include <cassert>
//------------------------------------------------------------------------
// Base
struct BaseExt {
std::optional<char const*> Base_var1 = std::optional<char const*>{};
std::optional<char const*> Base_var2 = std::optional<char const*>{};
};
struct Base;
struct BaseApply { BaseApply(Base&); };
struct Base : BaseExt {
void apply_defaults()
{
if (!Base_var1)
Base_var1 = "Base_var1";
// Always initialize Base_var2 yourself.
assert(Base_var2);
}
[[no_unique_address]] BaseApply dummy{*this};
};
BaseApply::BaseApply(Base& base) { base.apply_defaults(); }
//------------------------------------------------------------------------
// Derived1
struct Derived1Ext {
std::optional<char const*> derived1_var3 = std::optional<char const*>{};
};
struct Derived1;
struct Derived1Apply { Derived1Apply(Derived1&); };
struct Derived1 : BaseExt, Derived1Ext {
void apply_defaults()
{
if (!Base_var1)
Base_var1 = "Derived1_var1"; // Override default of Base!
if (!derived1_var3)
derived1_var3 = "Derived1_var3";
BaseExt* self = this;
static_cast<Base*>(self)->apply_defaults();
}
[[no_unique_address]] Derived1Apply dummy{*this};
};
Derived1Apply::Derived1Apply(Derived1& derived1) { derived1.apply_defaults(); }
//------------------------------------------------------------------------
// Derived2
struct Derived2Ext {
std::optional<char const*> derived2_var4 = std::optional<char const*>{};
std::optional<char const*> derived2_var5 = std::optional<char const*>{};
};
struct Derived2;
struct Derived2Apply { Derived2Apply(Derived2&); };
struct Derived2 : BaseExt, Derived1Ext, Derived2Ext {
void apply_defaults()
{
if (!derived2_var4)
derived2_var4 = "Derived2_var4";
if (!derived2_var5)
derived2_var5 = "Derived2_var5";
BaseExt* self = this;
static_cast<Derived1*>(self)->apply_defaults();
}
[[no_unique_address]] Derived2Apply dummy{*this};
};
Derived2Apply::Derived2Apply(Derived2& derived2) { derived2.apply_defaults(); }
//------------------------------------------------------------------------
int main()
{
Derived2 d2 = {
{ .Base_var2 = "main_var2"},
{ .derived1_var3 = "main_var3" },
{ .derived2_var5 = "main_var5" },
};
std::cout <<
*d2.Base_var1 << ", " <<
*d2.Base_var2 << ", " <<
*d2.derived1_var3 << ", " <<
*d2.derived2_var4 << ", " <<
*d2.derived2_var5 << std::endl;
}
This prints as output:
Derived1_var1, main_var2, main_var3, Derived2_var4, main_var5
^^^^^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^
Base_var1, Base_var2 derived1_var3 derived2_var5
default by derived2_var
Derived1. default by
Derived2.