std :: make_shared与std :: initializer_list

SH.*_*x90 17 c++ std c++11

#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;
}
Run Code Online (Sandbox Code Playgroud)

它的工作原理(实时预览)正常,但是当我尝试使用std::make_sharedstd::initializer_list作为参数,我得到了错误:

auto example = new Derived({
    { 0, std::make_shared<Derived>({
        { 0, std::make_shared<Derived>() }
    }) }
});
Run Code Online (Sandbox Code Playgroud)

正如您在实时预览中看到的那样.

错误:功能参数太多......

它仅适用于我这样做(实时预览):

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

我想知道的是:为什么它只在我传递std::initializer_listas参数std::make_shared而不是{{}}像这样使用时才起作用:

auto example = new Derived({ { 0, std::make_shared<Base>() } });
Run Code Online (Sandbox Code Playgroud)

有可能std::make_shared接受它吗?

提前致谢.

Bri*_*ian 17

之所以

auto example = new Derived({
    { 0, std::make_shared<Derived>() }
});
Run Code Online (Sandbox Code Playgroud)

工作是编译器知道它必须匹配初始化器

{{ 0, std::make_shared<Derived>() }}
Run Code Online (Sandbox Code Playgroud)

以某种方式与构造函数

Derived::Derived(std::initializer_list<std::pair<int, std::shared_ptr<Base>>>) {}
Run Code Online (Sandbox Code Playgroud)

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

{ 0, std::make_shared<Derived>() }
Run Code Online (Sandbox Code Playgroud)

需要用来初始化a std::pair<int, std::shared_ptr<Base>>.然后它找到一对带有两个元素的构造函数,

pair::pair (const first_type& a, const second_type& b);
Run Code Online (Sandbox Code Playgroud)

这里first_typeintsecond_typestd::shared_ptr<Base>.所以最后我们看到这个论点std::make_shared<Derived>()被隐含地转换成了std::shared_ptr<Base>,我们很高兴去!

在上面,我指出编译器通过查找一个构造函数来处理初始化程序列表,该构造函数直接接受初始化程序列表,或者适当数量的参数,然后在必要时进行适当的隐式转换后,将初始化程序列表的元素传递给它们.例如,编译器可以确定您std::shared_ptr<Derived>需要std::shared_ptr<Base>在上面的示例中隐式转换为仅因为该对的构造函数需要它.

现在考虑

std::make_shared<Derived>({
        { 0, std::make_shared<Derived>() }
    })
Run Code Online (Sandbox Code Playgroud)

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

{{ 0, std::make_shared<Derived>() }}
Run Code Online (Sandbox Code Playgroud)

它在重载解析时不知道它需要转换为std::initializer_list<std::pair<int, std::shared_ptr<Base>>>.另外,braced-init-list永远不会std::initializer_list<T>被模板推导推断出来,所以即使你有类似的东西

std::make_shared<Derived>({0, 0})
Run Code Online (Sandbox Code Playgroud)

并且Derived有一个适当的构造函数std::initializer_list<int>,它仍然无法工作,出于同样的原因:std::make_shared<Derived>无法推断出任何类型的参数.

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


Yak*_*ont 5

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

我会使用traits类将类型映射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) );
}
Run Code Online (Sandbox Code Playgroud)

或类似的东西.

另外,"剧组的" {...}initializer_list<blah>直接(不是演员,而是一个结构)可能工作.

从理论上讲,足够的反射元编程支持可以shared_ptr在没有特征类的情况下实现这一点,这一点在管道上相当远.