【问题标题】:How do I solve the ambiguity of brace-elisioning with list-initializers in C++?如何在 C++ 中使用列表初始化器解决大括号省略的歧义?
【发布时间】:2021-02-20 19:28:21
【问题描述】:

我想为 3x3 矩阵制作自己的结构。我想允许通过组件/元素或“行”进行构造。

因此,您要么提供std::array<float, 9> 要么提供std::array<std::array<float, 3>, 3>

但是,当使用以下构造函数定义这样的结构时:

struct Matrix3x3
{
    Matrix3x3(std::array<float, 9> components) { }

    Matrix3x3(std::array<std::array<float, 3>, 3> rows) { }
};

那么第二个构造函数与第一个构造函数有歧义。这意味着您可以像这样调用第二个构造函数

Matrix3x3{ {{ {{1.f, 2.f, 3.f}}, {{4.f, 5.f, 6.f}}, {{7.f, 8.f, 9.f}} }} };

没有任何问题,但是像这样调用第一个构造函数

Matrix3x3{ {{1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f}} };

将给出以下消息和错误:

message : No constructor could take the source type, or constructor overload resolution was ambiguous
error C2440: '<function-style-cast>': cannot convert from 'initializer list' to 'ArrayTest'

Visual Studio 告诉我“有多个构造函数实例...匹配参数列表”。

我测试了做同样的事情,但使用了一个整数数组(因为它更容易使用)和一个长度。考虑:

struct ArrayTest
{
    ArrayTest(std::array<int, 1> arrayOfInts) { }

    ArrayTest(std::array<std::array<int, 1>, 1> arrayOfArraysOfInts) { }
};

那么前 3 个是有效的,并且为第一个构造函数编译,而所有 5 个都为第二个构造函数编译。

auto test1 = ArrayTest{ {1} };

auto test2 = ArrayTest{ { {1} } };

auto test3 = ArrayTest{ { { {1} } } };

auto test4 = ArrayTest{ { { { {1} } } } };

auto test5 = ArrayTest{ { { { { {1} } } } } };

对于简单的数组构造函数“test3”是完整和完整的初始化,其中第一对括号初始化聚合ArrayTest,第二对初始化数组,第三对初始化该数组的第一个元素,最后是第四个括号初始化很少见但有效的整数 1。 “test1”和“test2”只是“test3”的大括号省略版本。

对于array-of-arrays 构造函数,“test5”是完整的初始化并且所有其他的都是大括号省略的。这就是造成歧义的原因。

所以问题是:我该如何解决这个问题?还是有更好的方法/解决方案?

【问题讨论】:

  • @jtbandes 不,不幸的是它没有,或者至少我不明白它会如何。这个问题不是关于为什么以及何时可以在初始化中省略大括号,而是为什么我的两个构造函数不明确。
  • @TheHelpfulHelper:这是模棱两可的,因为可以在给定大括号的情况下调用两个构造函数。大括号省略使得任何一个构造函数都可以在给定大括号的情况下被调用。
  • @NicolBolas 我重新考虑并重新定义了我的问题。我现在明白为什么它模棱两可了,但我仍然不知道如何优雅地解决这个问题。
  • 列表初始化使用列表初始化对象。它并没有避免暂时的。此外,虽然传递 std::array 作为参数对于编译器的优化应该是微不足道的,但我不太确定传递列表初始化程序。我很确定它的内容需要复制到数组中。

标签: c++ overload-resolution list-initialization brace-elision


【解决方案1】:

解决此问题的一种方法是单独声明和定义临时数组,如下所示:

std::array<float, 9> components{ 1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f };

std::array<float, 3> row1{ 1.f, 2.f, 3.f };
std::array<float, 3> row2{ 4.f, 5.f, 6.f };
std::array<float, 3> row3{ 7.f, 8.f, 9.f };

Matrix3x3 m1{ components };
Matrix3x3 m2{ { row1, row2, row3} };

这避免了使用列表初始化器进行大括号省略的歧义,但是这相当乏味并且似乎不是“最佳的”,因为那时您正在构建临时数组,只是为了构建其他东西,而列表点-在这种情况下,初始化将避免这些。

【讨论】:

    【解决方案2】:

    使用标签:一个空的结构,例如,struct by_row{};。然后你的 by-row 构造函数将它作为第一个参数,另一个没有。类似于&lt;algorithm&gt; 中的算法如何将“执行策略”作为第一个参数来区分并行版本和顺序版本。

    struct by_row {};
    
    struct Matrix3x3
    {
        Matrix3x3(std::array<float, 9> components) { ... }
    
        Matrix3x3(by_row, std::array<std::array<float, 3>, 3> rows) { ... }
    };
    

    调用看起来像:

        Matrix3x3 m{1,0,0,0,1,0,0,0,1};
        Matrix3x3 n{by_row{}, {{{1,0,0},{0,1,0},{0,0,1}}}};
    

    question linked in one of the comments above 中解释了奇怪的额外大括号。)

    (Godbolt)(其实我更喜欢this version。)

    【讨论】:

      猜你喜欢
      • 2017-10-03
      • 2013-06-03
      • 2012-07-28
      • 1970-01-01
      • 2023-03-11
      • 1970-01-01
      • 2013-08-23
      • 1970-01-01
      相关资源
      最近更新 更多