15

Can a std::initializer_list contain reference types (both rvalue and lvalue)? Or does one have to use pointers or a reference wrapper (such as std::ref)?

EDIT:

Perhaps more clarification is due:

I have a member variable, ::std::vector<std::function<void()> >, into which I would like to forward a lambda object. This would usually be accomplished with emplace_back, but I wanted to do it in the constructor's initialization list. Alas, as I read, this would make forwarding impossible.

user1095108
  • 14,119
  • 9
  • 58
  • 116

3 Answers3

13

Can a std::initializer_list contain reference types (both rvalue and lvalue)?

std::initializer_list<T> doesn't hold references to its elements. It uses copy-semantics by holding its values as const objects:

18.9 Initializer List [support.initlist]

An object of type initializer_list<E> provides access to an array of objects of type const E.

An initializer_list of references will cause a compilation error because iternally pointers are used for iterators:

#include <initializer_list>
int main()
{
    int x;
    std::initializer_list<int&> l = {x};

    // In instantiation of 'class std::initializer_list<int&>':
    // error: forming pointer to reference type 'int&'

    // typedef const _E*  iterator;
}

An initializer_list also doesn't support move-semantics as const objects cannot be moved from. Holding your objects in a std::reference_wrapper<T> is the most viable solution if you wish to maintain reference-semantics.

David G
  • 94,763
  • 41
  • 167
  • 253
  • I believe they do support a kind of move semantics, as you technically _can_ "move" a `const &&`. – user1095108 Jun 08 '14 at 19:08
  • 1
    @user1095108 No they don't. A move doesn't happen just from calling `std::move()`, it's simply a cast to an rvalue. A constructor taking a non-const rvalue reference `(X&&)` is where the movement actually happens, and "moving" a `const` object will actually create a copy in some cases, and in others a compilation error. – David G Jun 08 '14 at 19:14
  • 1
    I know, but nothing prevents a move constructor from copying, instead of moving, either. I'm just nitpicking. – user1095108 Jun 08 '14 at 19:16
8

From http://www.cplusplus.com/reference/initializer_list/initializer_list/

initializer_list objects are automatically constructed as if an array of elements of type T was allocated

thus they can't be used with something like std::initializer_list<int&>. The reason is the same for which the following gives a compiler error

int& arr[20];

error: declaration of ‘arr’ as array of references

and that is dictated by the C++ standard: https://stackoverflow.com/a/1164306/1938163

Community
  • 1
  • 1
Marco A.
  • 43,032
  • 26
  • 132
  • 246
2

You do not need list initialization here

As others mentioned, you cannot use std::initializer_list with references. You can use std::initializer_list<std::reference_wrapper<...>>, but it will prevent your from passing rvalues as arguments to the constructor, because std::reference_wrapper can only bind to lvalues. In other words, the following will not compile:

YourContainerOfFunctions C{ [](){} };

This makes usage of std::initializer_list in your case neither efficient nor convenient.

Use variadic templates instead!

I believe that is what you wanted to achieve:

class Foo {
  std::vector<std::function<void()>> Functions;

public:
  template <class... FuncTs>
  Foo(FuncTs &&...Funcs) : Functions({std::forward<FuncTs>(Funcs)...}) {}
};

void foo(){};

int main() {
  auto boo = []() {};
  std::function<void()> moo = []() {};

  Foo F{
      foo, boo,      // passed by reference, then copied
      []() {},       // moved, then copied
      std::move(moo) // moved, then also moved
  };
}

This requires at most one copy per argument, necessary because std::function always make a copy of functor object which it is constructed from. An exception is construction of std::function from std::function of the same type

Вовка
  • 176
  • 2
  • 5