Std:: make_shared с std:: initializer_list
#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_shared
с аргументом std::initializer_list
в качестве аргумента, я получил ошибки:
auto example = new Derived({
{ 0, std::make_shared<Derived>({
{ 0, std::make_shared<Derived>() }
}) }
});
Как вы можете видеть здесь на предварительном просмотре в прямом эфире.
error: слишком много аргументов для функции...
Работает только тогда, когда я делаю это (предварительный просмотр в прямом эфире):
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
его?
Спасибо заранее.
Ответы
Ответ 1
Для этого вам нужно создать пользовательский 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<blah>
напрямую (не литой, а скорее конструкцией) может работать.
В теории, достаточная поддержка метапрограммирования отражения позволила бы shared_ptr
сделать это без класса признаков, бит, который довольно далеко по трубе.
Ответ 2
Причина, по которой
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 (const first_type& a, const second_type& b);
где first_type
- int
, а second_type
- std::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>() }
})
Проблема заключается в том, что make_shared<Derived>
является частично специализированным шаблоном функции, который может принимать аргументы произвольного числа и типа. Из-за этого компилятор понятия не имеет, как обрабатывать список инициализаторов
{{ 0, std::make_shared<Derived>() }}
Во время разрешения перегрузки он не знает, что его нужно преобразовать в std::initializer_list<std::pair<int, std::shared_ptr<Base>>>
. Кроме того, список braced-init не выводится как std::initializer_list<T>
путем вычитания шаблона, поэтому даже если у вас есть что-то вроде
std::make_shared<Derived>({0, 0})
и Derived
имел соответствующий конструктор, принимающий std::initializer_list<int>
, он по-прежнему не работал бы по той же причине: std::make_shared<Derived>
не смог бы вывести любой тип для своего аргумента.
Как это исправить? К сожалению, я не вижу никакого простого способа. Но по крайней мере теперь вы должны знать, почему то, что вы написали, не работает.