使用 std::initializer_list 的 std::make_shared

22
#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;
}

它正常工作 (现场预览),但当我尝试使用std::initializer_list作为参数与std::make_shared一起使用时,我收到错误:

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

正如您在实时预览中所看到的。

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

只有这样做它才有效(实时预览):

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接受它?

先行致谢。


8
一个带大括号的初始化器不是一个表达式,也没有类型。因此,make_shared 的模板类型推断无法推断它的类型。 - dyp
2个回答

20

原因是为什么

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<int, std::shared_ptr<Base>>。然后,它会查找一个接受两个元素的pair构造函数。

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

其中first_typeint,而second_typestd::shared_ptr<Base>。因此,最终我们看到参数std::make_shared<Derived>()被隐式转换为std::shared_ptr<Base>,于是一切就绪!

在上面的例子中,我指出编译器通过查找直接接受初始化列表或适当数量参数的构造函数来处理初始化列表,并在必要时进行适当的隐式转换,将初始化列表的元素传递给该函数。例如,在上面的示例中,编译器之所以能够确定需要将std::shared_ptr<Derived>隐式转换为std::shared_ptr<Base>,是因为pair的构造函数要求这样做。

现在考虑以下内容:

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

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

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

在重载决议时,它并不知道需要转换为 std::initializer_list<std::pair<int, std::shared_ptr<Base>>>。此外,花括号初始化列表 永远不会被模板推断为 std::initializer_list<T>,因此即使您有以下内容:

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

即使Derived有一个接受std::initializer_list<int>的适当构造函数,它仍然无法工作,原因相同:std::make_shared<Derived>将无法推断其参数的任何类型。

如何修复这个问题?不幸的是,我看不到任何简单的方法。但至少现在您应该知道为什么您编写的代码无法正常工作。


9
为使此功能可用,您需要创建一个自定义的 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(不是转换而是构造)可能会起作用。理论上,足够的反射元编程支持可以使shared_ptr在没有特质类的情况下完成此操作,但这还有很长的路要走。

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