【问题标题】:std::initializer_list not able to be deduced from <brace-enclosed initializer list>std::initializer_list 不能从 <brace-enclosed initializer list> 中推导出来
【发布时间】:2016-05-15 19:55:00
【问题描述】:

我有一个类,其构造函数采用initializer_list:

Foo::Foo(std::initializer_list<Bar*> bars)

如果我尝试使用 大括号括起来的初始化列表 直接创建一个对象,则initializer_list 被正确推断:

Foo f ({ &b }); // std::initializer_list<Bar*> correctly deduced

但是,当尝试间接(使用可变参数函数模板 - 在本例中为 make_unique)执行相同操作时,编译器无法推断出 initializer_list

std::make_unique<Foo>({ &b }); // std::initializer_list<Bar*> not deduced

错误输出:

错误:没有匹配函数调用‘make_unique(&lt;brace-enclosed initializer list&gt;)’

问题:

  • 为什么编译器无法将{ &amp;b } 推断为initializer_list&lt;Boo*&gt;
  • 是否可以使用我想要的语法std::make_unique&lt;Foo&gt;({ &amp;b })

完整示例如下:

#include <initializer_list>
#include <memory>

struct Bar
{};

struct Foo
{
    Foo(std::initializer_list<Bar*> bars)
    { }
};

int main()
{
    Bar b;

    // initializer_list able to be deduced from { &b }
    Foo f ({ &b });

    // initializer_list not able to be deduced from { &b }
    std::unique_ptr<Foo> p = std::make_unique<Foo>({ &b });

    (void)f;
    return 0;
}

【问题讨论】:

标签: c++ c++11


【解决方案1】:

花括号初始化器没有类型。当您调用 make_unique 时,它会尝试推断类型并失败。在这种情况下,您必须在调用时指定类型

std::make_unique<Foo>(std::initializer_list<Bar*>{ &b });

这将创建一个std::initializer_list&lt;Bar*&gt;,编译器可以推导出它并将其转发给Foo::Foo(std::initializer_list&lt;Bar*&gt; bars)

Foo f ({ &amp;b }); 起作用的原因是编译器知道构造函数Foo(std::initializer_list&lt;Bar*&gt; bars)B*s 的大括号初始化列表可以隐式转换为std::initializer_list&lt;Bar*&gt;。没有进行类型扣除。

【讨论】:

  • 在第一种情况下 - Foo f ({ &amp;b }) - 如果大括号初始值设定项没有类型,推导出 Foo 的参数的规则是什么?也就是说,在第一个例子中,我们如何从 no typeinitializer_list
  • @SteveLorimer 刚刚将其添加到答案中。
【解决方案2】:

make_unique 使用完美转发。

完美转发在以下方面是不完美的:

  • 无法转发初始化列表

  • 它将NULL0 转换为整数,然后不能将其传递给指针类型的值。

  • 它不知道它的参数是什么类型,所以你不能做需要知道它们的类型的操作。举个例子:

    struct Foo { int x; };
    void some_funcion( Foo, Foo ) {};
    
    template<class...Args>
    decltype(auto) forwarding( Args&& ... args ) {
      return some_function(std::forward<Args>(args)...);
    }
    

    拨打some_function( {1}, {2} ) 是合法的。它使用{1}{2} 构造Foos。

    调用forwarding( {1}, {2} ) 不是。它在您调用forwarding 时不知道参数将为Foos,因此它无法构造它,并且它无法通过代码传递构造初始化器列表(因为构造列表不是变量或表达式)。

  • 如果传递重载函数名,则无法在调用点计算出该重载。并且一组重载不是一个值,所以你不能完美地转发它。

  • 您不能通过位域。

  • 它强制引用其参数,即使转发的目标没有。这会以可能导致程序在技术上不正确的方式“使用”一些静态 const 数据。

  • 无法转发对未知大小数组T(&amp;)[] 的引用。但是,您可以调用带有 T* 的函数。

其中大约一半来自this comp.std.c++ thread,当我想起还有其他问题时,我想不起来了。

【讨论】:

  • 您知道是否有任何工作可以解决这些缺陷?
  • @SteveLorimer:一般来说,没有。他们中的许多人都有足够的解决方法,例如使用nullptr 来绕过#2,并避免使用语言数组和位域。对于braced-init-list 问题,您唯一能做的就是使用显式类型。所有这些问题的最终问题是转发函数不使用它转发到的函数的签名。没有人表示有兴趣写一篇论文来允许这样做。反射可能有朝一日允许透明转发,但如果有的话,那就是 C++20。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-17
  • 1970-01-01
  • 2017-11-17
  • 1970-01-01
  • 2013-01-13
相关资源
最近更新 更多