How to Implement an Initializer List for User Defined Type? (Analogus to Std::Vector Initializer List)

How to implement an initializer list for user defined type? (analogus to std::vector initializer list)

Create a constructor which takes a std::initializer_list as a parameter:

#include <vector>
#include <initializer_list>

template <typename T>
struct foo
{
private:
std::vector<T> vec;

public:

foo(std::initializer_list<T> init)
: vec(init)
{ }
};

int main()
{
foo<int> f {1, 2, 3, 4, 5};
}

std::vector does this is almost exactly the same way (although utilizing begin() and end() - std::initializer_list has iterators much the same way as other containers do). From gcc:

  vector(initializer_list<value_type> __l,
const allocator_type& __a = allocator_type())
: _Base(__a)
{
_M_range_initialize(__l.begin(), __l.end(),
random_access_iterator_tag());
}

Edit: I'm not 100% what you're trying to do, but you may simply be able to use uniform initialization to get what you want:

struct bar
{
private:

int i;
double j;
std::string k;

public:

bar(int i_, double j_, const std::string& k_)
: i(i_), j(j_), k(k_)
{ }

};

int main()
{
bar b {1, 2.0, "hi"};
}

Unable to initialize vector from initializer list in delegating constructor

The initializer_list constructor for std::vector looks like:

vector(std::initializer_list<value_type>)

Where value_type is the first template argument to std::vector. In this case, std::vector<std::vector<float>> has value_type = std::vector<float> -- which means that it can only be constructed from a std::initializer_list<std::vector<float>> directly.

In order for this to work, you will need to update in one of the following ways:

  1. update your std::initializer_list to be std::initializer_list<std::vector<float>>,
  2. just use the std::vector<std::vector<float>> constructor and ignore using the initializer_list entirely1, or
  3. Range construct from the initializer_list<initializer_list<float>> in the constructor`:
    MyClass(std::initializer_list<std::initializer_list<float>> arg)
    : vec{arg.begin(), arg.end()}
    {
    // each iterator points to an initializer_list, which implicitly
    // constructs each vector
    }
    Edit: Since this question is asking with respect to delegating constructors, you would be stuck explicitly constructing a temporary vector-of-vectors first:
    MyClass(std::initializer_list<std::initializer_list<float>> arg)
    : MyClass{std::vector<std::vector<float>>{arg.begin(), arg.end()}}
    This is a case where delegating constructors probably aren't the best solution to your problem. See below.

1 Since std::vector already works properly with initializer lists, even when nested in other types, it would probably be easiest to just ignore adding an explicit std::initializer_list constructor entirely -- and just use move semantics properly in the implementation.

If you're using std::move properly, this constructor should be relatively cheap anyway -- which could make the initializer list functionality come for free.

List initialization (aka uniform initialization) and initializer_list?

List-initialization prefers constructors with a std::initializer_list argument. From cppreference:

The effects of list-initialization of an object of type T are:

[...cases that do not apply here ...]

  • Otherwise, the constructors of T are considered, in two phases:
    • All constructors that take std::initializer_list as the only argument, or as the first argument if the remaining arguments have
      default values, are examined, and matched by overload resolution
      against a single argument of type std::initializer_list

    • If the previous stage does not produce a match, all constructors of T participate in overload resolution against the set of arguments
      that consists of the elements of the braced-init-list, with the
      restriction that only non-narrowing conversions are allowed. If this
      stage produces an explicit constructor as the best match for a
      copy-list-initialization, compilation fails (note, in simple
      copy-initialization, explicit constructors are not considered at all).

While direct initialization does not prefer the initializer_list constructor. It calls the constructor that takes the size as argument.

And from https://en.cppreference.com/w/cpp/container/vector/vector:

Note that the presence of list-initializing constructor (10) means
list initialization and direct initialization do different things:

std::vector<int> b{3}; // creates a 1-element vector holding {3}
std::vector<int> a(3); // creates a 3-element vector holding {0, 0, 0}
std::vector<int> d{1, 2}; // creates a 2-element vector holding {1, 2}
std::vector<int> c(1, 2); // creates a 1-element vector holding {2}


And how can I call std::vector::vector(size_t) with list-initialization?

As explained above, the presence of the std::initializer_list constructor prevents you from calling the other constructor via list-initialization.

Initializer list vs. vector

The common use of std::initializer_list is as argument to constructors of container (and similar) classes, allowing convenient initialisation of those containers from a few objects of the same type.
Of course, you can use std::initializer_list otherwise and then use the same {} syntax.

Since a std::initializer_list has a fixed size, it doesn't require dynamic allocation and hence can be efficiently implemented. A std::vector, on the other hand, requires dynamic memory allocation. Even in your simple example it is unlikely that the compiler will optimize this overhead away (avoid the intermediary std::vector and its dynamic memory allocation). Other than that, there is no difference in the outcome of your programs (though you should take a const std::vector<int>& argument to avoid a copy and its associated dynamic memory allocation).

Initializer list in user-defined literal parameter

you'd expect syntax to be

if value in (value1, value2 ...) 

or something similar.

If you're willing to add one extra character, try this syntax:

#include <algorithm>
#include <iostream>
#include <array>

template <typename T0, typename T1, std::size_t N>
bool operator *(const T0& lhs, const std::array<T1, N>& rhs) {
return std::find(begin(rhs), end(rhs), lhs) != end(rhs);
}

template<class T0, class...T> std::array<T0, 1+sizeof...(T)> in(T0 arg0, T...args) {
return {{arg0, args...}};
}

int main () {
if( 2 *in(1,2,3) ) { std::cout << "Hello\n"; }
if( 4 *in(5,6,7,8) ) { std::cout << "Goodbye\n"; }
}

Deduction guide for brace initializer list

in an environment where standart C++ library is not available

There is no such thing. While freestanding C++ implementations are free to only implement parts of the standard library, there are some components which all valid C++ implementations must provide. std::initializer_list is among these components.

As such, if you have a valid C++11 or higher implementation of C++, then you must have the <initializer_list> header and its contents. This is not optional. If your implementation doesn't provide one, then it is defective.

The reason it is not optional is that the important functionality of std::initializer_list (that is, its generation from a braced-init-list) is a function of the C++ language, not of the library. That is, it is impossible for code outside of the compiler to make the {} grammatical construct become a type that is exactly analogous to how std::initializer_list behaves.

Consider your code:

user<int> sample { 1, 2, 3, 4, 5 };

If you think about it, this ought to mean that a constructor of user<int> will be called that takes 5 parameters. That's what it would mean if user had a constructor of 5 integer parameters, after all. But that's not what you want it to mean, and it wouldn't mean that for vector<int>. Why?

Because C++'s language has a special rule about list initialization that detects the presence of a constructor which takes a std::initializer_list that matches the braced-init-list types, and then creates a std::initializer_list to pass to this constructor. This rule keys off of the presences of a constructor that takes std::initializer_list and no other type.

Your code does not work, not because of a lack of deduction guides, but because your initializer_list type has no special rules as far as the language is concerned.

You cannot recreate this language behavior with a user-defined type. Just as you cannot make typeid return a type other than std::type_info. Just as you cannot make enum class byte: unsigned char{}; have the same behavior as std::byte.

How to initialize a vector in C++

With the new C++ standard (may need special flags to be enabled on your compiler) you can simply do:

std::vector<int> v { 34,23 };
// or
// std::vector<int> v = { 34,23 };

Or even:

std::vector<int> v(2);
v = { 34,23 };

On compilers that don't support this feature (initializer lists) yet you can emulate this with an array:

int vv[2] = { 12,43 };
std::vector<int> v(&vv[0], &vv[0]+2);

Or, for the case of assignment to an existing vector:

int vv[2] = { 12,43 };
v.assign(&vv[0], &vv[0]+2);

Like James Kanze suggested, it's more robust to have functions that give you the beginning and end of an array:

template <typename T, size_t N>
T* begin(T(&arr)[N]) { return &arr[0]; }
template <typename T, size_t N>
T* end(T(&arr)[N]) { return &arr[0]+N; }

And then you can do this without having to repeat the size all over:

int vv[] = { 12,43 };
std::vector<int> v(begin(vv), end(vv));

Initializer-list-constructing a vector of noncopyable (but movable) objects

Maybe this clause from 8.5.4.5 explains it (my emphasis):

An object of type std::initializer_list is constructed from an
initializer list as if the implementation allocated an array of N
elements of type E, where N is the number of elements in the
initializer list. Each element of that array is copy-initialized
with the corresponding element of the initializer list
, and the
std::initializer_list object is constructed to refer to that array.

So you can only initialize from lists if the objects are copyable.


Update: As Johannes points out, copy-initialization can be realized by both copy and move constructors, so that alone isn't enough to answer the question. Here is, however, an excerpt of the specification of the initializer_list class as described in 18.9:

  template<class _E>
class initializer_list
{
public:
typedef _E value_type;
typedef const _E& reference;
typedef const _E& const_reference;
typedef size_t size_type;
typedef const _E* iterator;
typedef const _E* const_iterator;

Note how there are no non-constant typedefs!

I just tried making an IL constructor which would traverse the initializer list via std::make_move_iterator, which failed because const T & cannot be converted to T&&.

So the answer is: You cannot move from the IL, because the standard says so.

Pair of vector constructors: initializer list vs explicit construction

The problem comes from std::pair constructors and template argument deduction / overload resolution:

pair( const T1& x, const T2& y ); // (1)

template< class U1, class U2 >
pair( U1&& x, U2&& y ); // (2)

Note that there is, at least, one "missing" constructor:

pair( T1&& x, T2&& y );           // (3)

When you use list-initialization for the first parameter, the selected constructor is not (2) (with U1 = std::initializer_list<int>) but (1)1. Thus, you need to construct a temporary std::vector<int>, which is passed as a const-reference to (1), which has to make a copy.

You can confirm empirically this by either:

  • creating your own pair with the third constructor mentioned above — in this case, (3) will be chosen, and the temporary vector will be moved;
  • explicitly constructing an std::initializer_list<int> while constructing the std::pair:
pair<vector<int>, int>{ std::initializer_list<int>{1,2,3,4,5}, 1 };

On the other hand std::optional as a single templated constructor:

template < class U = value_type >
constexpr optional( U&& value );

...but there is a default value for U, which makes this constructor a valid candidate for overload resolution.


1 When you call pair{ {1,2,3,4,5}, 1 }, U1 is in a non-deduced context within (2) [temp.deduct.type]#5.6, so deduction fails, which why (1) is selected.



Related Topics



Leave a reply



Submit