【问题标题】:Constructor ambiguity with array initializer构造函数与数组初始值设定项的歧义
【发布时间】:2020-02-04 10:22:26
【问题描述】:

我有一个问题,即在使用 VS2017(C++14、C++17 和 ISO 最新版本)时,我无法将某个构造函数与数组初始值设定项一起使用。

当它应该使用填充了单个元素的容器调用构造函数时,我收到 C2397 conversion from 'double' to 'unsigned int' requires a narrowing conversion 错误。

#include <vector>

class obj
{
public:
    obj(const std::vector<double>& values, unsigned int stride)
        : values_(values), stride_(stride)
    {
    }

    obj(unsigned int m, unsigned int n)
        : stride_(n)
    {
    }

private:
    unsigned int stride_;
    std::vector<double> values_;
};

int main(int argc, char** argv)
{
    obj m(1, 1);                // correct constructor called.
    obj mm({ 42.0 }, 1);        // Error C2397

    return 0;
}

我可以通过明确声明容器来解决这个问题...

    obj mm(std::vector<double>({ 42.0 }), 1);

或者用多个项目初始化容器...

    obj mm({ 42.0, 12.0 }, 1);

后者显然没有用,前者有点烦人,因为它是带有单个物品的容器的角落案例(尽管不是世界末日)。我认为这可能仅对双精度数(没有文字声明)有问题,但是在用文字初始化浮点数时甚至会发生这种情况。即容器是std::vector&lt;float&gt;,以下行仍然与C2397错误。

    obj mm({ 42.0f }, 1);

我不倾向于相信自己没有遇到很多编译器错误(尽管它们显然存在),但是我不禁认为这可能是一个,或者如果不是,标准中是否有任何提及如何处理这种情况。理想情况下,当容器中存在多个项目时,我希望能够在不显式声明容器类型的情况下使用数组初始值设定项。这可能吗?

【问题讨论】:

  • g++ 9.2 说:error: narrowing conversion of '4.2e+1' from 'double' to 'unsigned int' [-Wnarrowing]error: narrowing conversion of '4.2e+1f' from 'float' to 'unsigned int' [-Wnarrowing] 在浮动情况下。
  • @Scheff 谢谢,是的,这是一个修复。

标签: c++ visual-studio-2017 array-initialization


【解决方案1】:

使用{{}} 是解决所有情况的方法

obj mm({{ 42.0 }}, 1); 

obj mm({{ 42.0, 12.0 }}, 1);

虽然在第二种情况下当然没有歧义(使用单括号是利用 brace-elision)。

这个问题很好地介绍了这个主题:Brace elision in std::array initialization

【讨论】:

  • 谢谢,是的,这似乎是答案。我猜它要么尝试强制执行此语法,要么为单个项目填充容器的单个案例提供 initializer_list 构造函数。
  • 大括号省略对我来说是新的术语和行为。谢谢,我会消化你发布的主题:)
【解决方案2】:

你的意思是以下

obj mm({ 1, 42.0 }, 1);

或以下

obj mm({ { 42.0 } }, 1);

【讨论】:

  • 谢谢你的回答,我的意思是后者。
【解决方案3】:

可以将带有std::initializer_list 的构造函数添加到对象中。

示例:

#include <iostream>
#include <vector>

struct Obj {
  std::vector<double> values;
  unsigned stride;

  Obj(std::initializer_list<double> values, unsigned stride = 1):
    values(values), stride(stride)
  {
    std::cout << "Obj::Obj(std::initializer_list<double>, unsigned)\n";
   }

  Obj(const std::vector<double> &values, unsigned stride = 1):
    values(values), stride(stride)
  {
    std::cout << "Obj::Obj(const std::vector<double>&, unsigned)\n";
  }

  Obj(unsigned m, unsigned stride = 1):
    stride(stride)
  {
    std::cout << "Obj::Obj(unsigned, unsigned)\n";
  }
};

int main()
{
  Obj mm({ 42.0f }, 1);
  Obj mm1(1, 1);
  Obj mm2(std::vector<double>({ 42.0 }), 1);
  Obj mm3({ 42.0, 12.0 }, 1);
  Obj mm4(std::vector<double>{ 42.0 }, 1);
}

输出:

Obj::Obj(std::initializer_list<double>, unsigned)
Obj::Obj(unsigned, unsigned)
Obj::Obj(const std::vector<double>&, unsigned)
Obj::Obj(std::initializer_list<double>, unsigned)
Obj::Obj(const std::vector<double>&, unsigned)

Live Demo on coliru

【讨论】:

  • std::vector&lt;double&gt;{ 42.0 } 看起来更好 IMO。
  • @MarekR 我同意。我类似于 OP 的示例来演示选择了哪个构造函数。 (我有点不确定在初始化列表和const std::vector&lt;&gt;&amp; 之间的选择是否会导致我不知道的歧义。初始化的东西对我来说还是有点新。);-)
  • 感谢您的回答和示例,是的,这确实解决了问题。我不确定我觉得最好是记下使用双花括号语法,还是为具有单个项目的容器的单个案例提供 initializer_list 构造函数。不过还是很感谢你的回答,谢谢:)
  • @lfgtm:如果我是你,我会为“未省略”的语法感到满意。单括号语法依赖于我认为是 C++ 的一个棘手的角落。
  • @lfgtm 实际上,我部分类似于std::vector 提供的构造。遵循 std 库的 API 恕我直言,这并不是最糟糕的事情。 ;-)(关于 API 用户可能有也可能没有的期望)
猜你喜欢
  • 2013-06-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-21
相关资源
最近更新 更多