【问题标题】:std::make_shared with std::initializer_liststd::make_shared 与 std::initializer_list
【发布时间】:2014-06-16 08:42:42
【问题描述】:
#include <iostream>
#include <memory>

class Base
{
public:
    Base() {}
};

class Derived : public Base
{
public:
    Derived() {}
    Derived(std::initializer_list<std::pair<int, std::shared_ptr<Base>>>) {}
};

int main(int argc, char ** argv)
{
    auto example = new Derived({
        { 0, std::make_shared<Derived>() }
    });

    return 0;
}

它可以正常工作 (live preview),但是当我尝试使用 std::make_sharedstd::initializer_list 作为参数时出现错误:

auto example = new Derived({
    { 0, std::make_shared<Derived>({
        { 0, std::make_shared<Derived>() }
    }) }
});

正如您在live preview 上看到的那样。

错误:函数参数太多...

只有当我这样做时它才有效 (live preview):

auto example = new Derived({
    { 0, std::make_shared<Derived>(std::initializer_list<std::pair<int, std::shared_ptr<Base>>> {
        { 0, std::make_shared<Derived>() }
    }) }
});

我想知道的是:为什么它只在我将std::initializer_list 作为std::make_shared 的参数而不是像这样使用{{}} 时才有效:

auto example = new Derived({ { 0, std::make_shared<Base>() } });

这样可以让std::make_shared接受吗?

提前致谢。

【问题讨论】:

  • 花括号初始值设定项不是表达式,也没有类型。因此make_shared的模板类型推导无法推导出来。

标签: c++ c++11 std


【解决方案1】:

原因

auto example = new Derived({
    { 0, std::make_shared<Derived>() }
});

有效的是编译器知道它必须匹配初始化程序

{{ 0, std::make_shared<Derived>() }}

不知何故与构造函数

Derived::Derived(std::initializer_list<std::pair<int, std::shared_ptr<Base>>>) {}

所以很明显初始化列表的元素,

{ 0, std::make_shared<Derived>() }

需要用于初始化std::pair&lt;int, std::shared_ptr&lt;Base&gt;&gt;。然后它找到一个包含两个元素的对的构造函数,

pair::pair (const first_type& a, const second_type& b);

其中first_typeintsecond_typestd::shared_ptr&lt;Base&gt;。所以最后我们看到参数std::make_shared&lt;Derived&gt;() 被隐式转换为std::shared_ptr&lt;Base&gt;,我们可以开始了!

在上面,我指出编译器通过寻找一个构造函数来处理初始化列表,该构造函数直接接受初始化列表或适当数量的参数,然后在适当的隐式转换后将初始化列表的元素传递给该构造函数如有必要。例如,编译器可以确定您的std::shared_ptr&lt;Derived&gt; 需要在上面的示例中隐式转换为std::shared_ptr&lt;Base&gt;,只是因为pair 的构造函数需要它。

现在考虑

std::make_shared<Derived>({
        { 0, std::make_shared<Derived>() }
    })

问题在于make_shared&lt;Derived&gt; 是一个部分专用的函数模板,它可以接受任意数量和类型的参数。因此,编译器不知道如何处理初始化列表

{{ 0, std::make_shared<Derived>() }}

在重载解析时并不知道需要转换为std::initializer_list&lt;std::pair&lt;int, std::shared_ptr&lt;Base&gt;&gt;&gt;。此外,braced-init-list 永远不会通过模板推导推断为 std::initializer_list&lt;T&gt;,所以即使你有类似的东西

std::make_shared<Derived>({0, 0})

Derived 有一个适当的构造函数采用std::initializer_list&lt;int&gt;,它仍然无法工作,原因相同:std::make_shared&lt;Derived&gt; 将无法为其参数推断任何类型。

如何解决这个问题?不幸的是,我看不到任何简单的方法。但至少现在你应该知道为什么你写的东西不起作用了。

【讨论】:

    【解决方案2】:

    为此,您需要创建一个自定义make_shared_from_list,因为make_shared 不支持非显式初始化列表。 @brian 很好地描述了原因。

    我会使用特征类将类型 T 映射到初始值设定项列表的类型。

    template<class>struct list_init{};// sfinae support
    template<> struct list_init<Derived>{using type=std::pair<int, std::shared_ptr<Base>>;};
    
    template<class T>using list_init_t=typename list_init<T>::type;
    
    template<class T>
    std::shared_ptr<T> make_shared_from_list( std::initializer_list<list_init_t<T>> list ){
      return std::make_shared<T>( std::move(list) );
    }
    

    或类似的东西。

    或者,将{...} 直接“转换”为initializer_list&lt;blah&gt;(不是转换,而是构造)可能会起作用。

    理论上,足够的反射元编程支持将允许shared_ptr 在没有特征类的情况下执行此操作,这一点还很遥远。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-03-22
      • 1970-01-01
      • 2021-09-14
      • 2014-08-10
      • 1970-01-01
      • 2021-06-05
      • 2020-06-11
      相关资源
      最近更新 更多