使用指定初始化器重载结构默认值

Overload struct default values with Designated Initializers

是否有可能实现(至少类似于)这个? 我需要为“命名参数”指定初始值设定项 and/or 跳过某些参数设置的可能性(此处未显示)。并且仍然得到这个“级联”默认值。

理想情况下,我需要在不了解继承的情况下设置 Derived 的参数(实例化时)(因为应该有 5 级继承,它的用户不友好必须知道有多少继承......)当然知识需要参数名称和顺序。

#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;
}

我试着澄清一下:

如果不需要默认值(或者每个成员只需要一个),我可以这样做:

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;
}

但问题是: 如果我构造了 /Derived/,我需要为 /withDefault/ 使用不同的默认值。所以像这样:

struct Derived : public Base
{
    string withDefault = "useThisAsDefaultHere";
    string inDerived; /* no matter with/without default*/
};

考虑以下“幼稚”设计:

#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";
};

这里我们有五个变量,分别以_var1_var2_var3_var4_var5结尾。它们的前缀是它们最初定义的 class 的名称。例如,Base 定义 Base_var1。虽然 Derived1 覆盖了默认值,但它在 Derived1 当然仍然具有相同的名称。

因此我们可以声明 _var1Base 中有一个默认值,在 Derived1 中被覆盖。 _var2没有默认值,_var3-5分别在Derived1Derived2中引入,有默认值。

如果现在我们想要构造一个类型为 Derived2 的对象,我们希望在其中使用所有默认值,除了 _var3_var5(当然还有 _var2 一个值)然后我们可以尝试按如下方式执行此操作:

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;
}

这有几个缺陷。最重要的是它不是正确的 C++。当我们尝试用 clang++ 编译它时,我们得到:

>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.

程序的输出是:

Derived1_var1, main_var2, main_var3, Derived2_var4, main_var5

这是想要的结果。

使用 clang++ 你只会得到一个警告,但是使用 g++ 例如,它不会编译并且你会得到错误:

>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" };
      |                       ^

第二个问题是 Derived1 并没有真正覆盖 Base::Base_var1 的值,而是隐藏了它。如果将它传递给一个接受 Base& 的函数,那么 Base_var1 将具有“意外”值。

我解决第一个问题的方法是将结构拆分为定义变量和(初始)默认值的 *Ext(扩展)结构,并使用不带成员变量的 classes遗产。这样你就不用运行变成指定和非指定混合使用的需要了:

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 {
};

Derived2 的初始化变成:

  Derived2 d2 = {
    { .Base_var2 = "main_var2" },
    { .derived1_var3 = "main_var3" },
    { .derived2_var5 = "main_var5" }
  };

它有额外的大括号,但总的来说相当干净 - 您仍然只需要指定您想要与默认值不同的值。

当然,这个还是缺少对默认Base_var1的覆盖。

但我们可以在没有错误或警告的情况下编译它 ;)。然后输出

Base_var1, main_var2, main_var3, Derived2_var4, main_var5

最后一步是固定这里的第一个值,不再更改main()

我能想到的唯一方法是使用魔法值...在上面的例子中我们只有 char const* 但总的来说这可能会变得相当复杂。不过这里有一些东西可以解决问题:

#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;
}

编辑: 改进版

您可以使用 std::optional 而不是 use_default 幻数。不必手动调用 apply_defaults,您可以添加其他成员 (dummy) 来为您执行此操作。通过添加 [[no_unique_address]] 此添加(可能)完全优化掉(结构的大小不会增加(使用 std::optional 确实会使结构增加,当然))。

#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;
}

这打印为输出:

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.