构造函数与数组初始化器的歧义性

4

我遇到了一个问题,在使用VS2017(C++14,C++17和最新的ISO)时,我无法在数组初始化器中使用特定的构造函数。

当应该使用填充有单个元素的容器调用构造函数时,我会收到一个C2397 将“double”转换为“unsigned int”需要缩小转换错误提示。

#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<float>,以下代码行仍然会出错C2397
    obj mm({ 42.0f }, 1);

个人认为编译器错误并不常见(尽管它们显然存在),但我不能不认为这可能是一个编译器错误。如果不是,那么标准中有没有提到如何处理这种情况?理想情况下,当容器中存在多个项时,我希望能够在不明确声明容器类型的情况下使用数组初始化程序。这可以实现吗?


g++ 9.2报错:在float情况下,'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]'。 - Benjamin Bihler
@Scheff 谢谢,是的,这是一个修复。 - lfgtm
3个回答

5

在所有情况下,使用{{}}来解决问题。

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

并且

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

尽管第二种情况没有歧义(使用单个大括号是利用大括号省略),但这个问题提供了一个很好的主题介绍:Brace elision in std::array initialization

谢谢,是的,这似乎是答案。我猜要么尝试强制执行这个语法,要么为单个项目填充容器提供initializer_list构造函数。 - lfgtm
花括号省略对我来说是一个新的术语和行为。谢谢,我会消化你发布的主题 :) - lfgtm

1

可以向对象添加带有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)

在Coliru上的实时演示


在我看来,std::vector<double>{ 42.0 } 更好。 - Marek R
@MarekR 我同意。我使用OP的样例来演示哪个构造函数被选择。(我有点不确定初始化列表和const std::vector<>&之间的选择是否会引起我不知道的歧义。初始化的东西对我来说仍然有点新。);-) - Scheff's Cat
感谢您的回答和示例,是的,这确实解决了问题。我不确定哪种方法更好,是在注释中说明使用双花括号语法,还是为仅包含单个项目的容器提供initializer_list构造函数。无论如何,非常感谢您的回答 :) - lfgtm
如果我是你,我会选择“未省略”的语法。单括号语法依赖于我认为是C++的棘手角落。 - Bathsheba
1
@lfgtm 实际上,我在构建时部分地类似于std::vector提供的功能。遵循std库的API并不是最糟糕的事情。;-)(关于API用户可能具有或不具有的期望) - Scheff's Cat

1
你是指以下内容吗?
obj mm({ 1, 42.0 }, 1);

或者以下的。
obj mm({ { 42.0 } }, 1);

谢谢您的回答,我是指后者。 - lfgtm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接