6

I have a class whose constructor takes an initializer_list:

Foo::Foo(std::initializer_list<Bar*> bars)

If I attempt to create an object with a brace-enclosed initializer list directly, the initializer_list is correctly deduced:

Foo f ({ &b }); // std::initializer_list<Bar*> correctly deduced

However, when trying to do the same indirectly (with a variadic function template - in this case make_unique), the compiler is unable to deduce the initializer_list:

std::make_unique<Foo>({ &b }); // std::initializer_list<Bar*> not deduced

Error output:

error: no matching function for call to ‘make_unique(<brace-enclosed initializer list>)’

Questions:

  • Why is the compiler failing to deduce { &b } as a initializer_list<Boo*>?
  • Is it possible to use the syntax std::make_unique<Foo>({ &b }) which I desire?

Full example below:

#include <initializer_list>
#include <memory>

struct Bar
{};

struct Foo
{
    Foo(std::initializer_list<Bar*> bars)
    { }
};

int main()
{
    Bar b;

    // initializer_list able to be deduced from { &b }
    Foo f ({ &b });

    // initializer_list not able to be deduced from { &b }
    std::unique_ptr<Foo> p = std::make_unique<Foo>({ &b });

    (void)f;
    return 0;
}
Steve Lorimer
  • 27,059
  • 17
  • 118
  • 213
  • 1
    Note that there is no template type deduction in `Foo f ({ &b });`, whereas in `std::make_unique` is. – vsoftco Feb 05 '16 at 18:28
  • As a side note: Use `auto`. The whole point of `make_unique` is to be able to use type deduction. Otherwise it would be simpler to just write `std::unique_ptr p(new Foo{ &b });` – Not a real meerkat Feb 05 '16 at 18:30
  • Possible duplicate of [Creating a shared\_ptr of vector in C++](http://stackoverflow.com/questions/34769617/creating-a-shared-ptr-of-vector-in-c) – Starl1ght Feb 05 '16 at 18:43
  • @Starl1ght a better duplicate would be [Calling initializer_list constructor via make_unique/make_shared](http://stackoverflow.com/questions/26379311/calling-initializer-list-constructor-via-make-unique-make-shared), but Yakk's answer to this question is the best answer out of all 3 questions – Steve Lorimer Feb 05 '16 at 18:50

2 Answers2

5

make_unique uses perfect forwarding.

Perfect forwarding is imperfect in the following ways:

  • It fails to forward initializer lists

  • It converts NULL or 0 to an integer, which can then not be passed to a value of pointer type.

  • It does not know what type its arguments will be, so you cannot do operations that require knowing their type. As an example:

    struct Foo { int x; };
    void some_funcion( Foo, Foo ) {};
    
    template<class...Args>
    decltype(auto) forwarding( Args&& ... args ) {
      return some_function(std::forward<Args>(args)...);
    }
    

    Calling some_function( {1}, {2} ) is legal. It constructs the Foos with {1} and {2}.

    Calling forwarding( {1}, {2} ) is not. It does not know at the time you call forwarding that the arguments will be Foos, so it cannot construct it, and it cannot pass the construction-initializer-list through the code (as construction-lists are not variables or expressions).

  • If you pass an overloaded function name, which overload cannot be worked out at the point of call. And a set of overloads is not a value, so you cannot perfect forward it through.

  • You cannot pass bitfields through.

  • It forces takes a reference to its arguments, even if the forwarded target does not. This "uses" some static const data in ways that can cause a program to be technically ill-formed.

  • A reference to an array of unknown size T(&)[] cannot be forwarded. You can call a function taking a T* with it, however.

About half of these were taken from this comp.std.c++ thread, which I looked for once I remembered there were other issues I couldn't recall off the top of my head.

Steve Lorimer
  • 27,059
  • 17
  • 118
  • 213
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • do you know if there is any work to resolve these imperfections? – Steve Lorimer Feb 05 '16 at 18:45
  • 1
    @SteveLorimer: Generally speaking, no. Many of them have adequate workarounds, such as using `nullptr` to get around #2, and avoiding language arrays and bitfields. The only thing you can do about the braced-init-list problem is to use an explicit type. The ultimate problem that all of these issues are derived from is that the forwarding function does not use the signature of the function it is forwarding to. And nobody has expressed any interest in writing a paper to allow that. Reflection might someday allow transparent forwarding, but that'd be C++20 if ever. – Nicol Bolas Feb 05 '16 at 18:53
4

A braced initializer has no type. When you call make_unique it tries to deduce the type and fails. In this case you have to specify the type when calling like

std::make_unique<Foo>(std::initializer_list<Bar*>{ &b });

This will create a std::initializer_list<Bar*> which the compiler can deduce and it will forward it to Foo::Foo(std::initializer_list<Bar*> bars)

The reason Foo f ({ &b }); works is that the compiler knows of the constructor Foo(std::initializer_list<Bar*> bars) and braced initializer list of B*s can be implicitly converted to a std::initializer_list<Bar*>. There is not type deduction going on.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • What are the rules for deducing the argument to `Foo` in the first case - `Foo f ({ &b })` - if a braced initializer has no type? That is, how can we go from *no type* to `initializer_list` in the first example? – Steve Lorimer Feb 05 '16 at 18:37
  • 1
    @SteveLorimer Just added that into the answer. – NathanOliver Feb 05 '16 at 18:41