Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::make_shared with std::initializer_list

Tags:

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

It works (live preview) normally, but when I try to use std::make_shared with the std::initializer_list as argument I got errors:

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

As you can see here on the live preview.

error: too many arguments to function...

It works only when I do this (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>() }     }) } }); 

What I want to know is: Why it works only when I pass the std::initializer_list as argument on std::make_shared instead of using {{}} just like this:

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

Is that possible to make std::make_shared accept it?

Thanks in advance.

like image 567
SH.0x90 Avatar asked Jun 15 '14 21:06

SH.0x90


2 Answers

The reason why

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

works is that the compiler knows that it has to match the initializer

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

somehow with the constructor

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

So it is clear that the element of the initializer list,

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

needs to be used to initialize a std::pair<int, std::shared_ptr<Base>>. It then finds a constructor for the pair that takes two elements,

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

where first_type is int and second_type is std::shared_ptr<Base>. So finally we see that the argument std::make_shared<Derived>() is implicitly converted to std::shared_ptr<Base>, and we're good to go!

In the above, I pointed out that the compiler handles initializer lists by looking for a constructor that accepts either an initializer list directly, or the appropriate number of arguments, to which the initializer list's elements are then passed, after appropriate implicit conversions if necessary. For example, the compiler can figure out that your std::shared_ptr<Derived> needs to be implicitly converted to std::shared_ptr<Base> in the above example only because the pair's constructor demands it.

Now consider

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

The problem is that make_shared<Derived> is a partially specialized function template that can accept arguments of arbitrary number and type. Because of this, the compiler has no idea how to handle the initializer list

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

It doesn't know at the time of overload resolution that it needs to be converted to std::initializer_list<std::pair<int, std::shared_ptr<Base>>>. Additionally, a braced-init-list is never deduced as std::initializer_list<T> by template deduction, so even if you had something like

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

and Derived had an appropriate constructor taking std::initializer_list<int>, it still wouldn't work, for the same reason: std::make_shared<Derived> would not be able to deduce any type for its argument.

How to fix this? Unfortunately, I cannot see any easy way. But at least now you should know why what you wrote doesn't work.

like image 63
Brian Bi Avatar answered Nov 21 '22 20:11

Brian Bi


For this to work, you need to create a custom make_shared_from_list, as make_shared does not support non-explicit initializer lists. The reason is described well by @brian.

I would use a traits class to map a type T to the type of initializer list.

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) ); } 

or something like that.

Alternatively, "cast" the {...} to the initializer_list<blah> directly (not a cast, but rather a construction) may work.

In theory, sufficient reflection metaprogramming support would allow shared_ptr to do this without the traits class, bit that is pretty far down the pipe.

like image 30
Yakk - Adam Nevraumont Avatar answered Nov 21 '22 22:11

Yakk - Adam Nevraumont