Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does std::min(std::initializer_list<T>) take arguments by value?

Reading the answer to this question, I was surprised to find that std::min(std::initializer_list<T>) takes its arguments by value.

If you use std::initializer_list in the way implied by its name, i.e. as an initializer to some object, I understand that we don't care about copying its elements since they will be copied anyway to initialize the object. However, in this case here we most likely don't need any copy, so it would seem much more reasonable to take the arguments as std::initializer_list<const T&> if only it were possible.

What's the best practice for this situation? Should you not call the initializer_list version of std::min if you care about not making unnecessary copies, or is there some other trick to avoid the copy?

like image 413
gTcV Avatar asked Sep 24 '14 15:09

gTcV


2 Answers

There is no such thing as std::initializer_list<const T&>. Initializer lists can only hold objects by value.

See [dcl.init.list]/5:

An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated a temporary array of N elements of type const E, where N is the number of elements in the initializer list.

There are no arrays of references, so there cannot be an initializer_list of references either.

My suggestion in this circumstance would be to write a replacement for std::min which takes a variadic template of forwarding references. This works (although I'm sure it can be improved):

template<typename T, typename... Args>
T vmin( T arg1, Args&&... args )
{
    T *p[] = { &arg1, &args... };

    return **std::min_element( begin(p), end(p), 
            [](T *a, T *b) { return *a < *b; } );
}

See it working - using min or mmin, two extra copies are made (for a and b).

like image 159
M.M Avatar answered Nov 25 '22 12:11

M.M


To sum up the comments:

An std::initializer_list is supposed to be a lightweight proxy as described on cppreference.com. Therefore a copy of an initializer list should be very fast since the underlying elements are not copied:

C++11 §18.9/2

An object of type initializer_list<E> provides access to an array of objects of type const E. [ Note: A pair of pointers or a pointer plus a length would be obvious representations for initializer_list. initializer_list is used to implement initializer lists as specified in 8.5.4. Copying an initializer list does not copy the underlying elements. — end note ]

Using a reference though would boil down to using a pointer and therefore an additional indirection.

The actual problem of std::min therefore is not that it takes the initializer_list by value, but rather, that the arguments to initializer_list have to be copied if they are computed at runtime. [1] That the benchmark at [1] was broken as found out later is unfortunate.

auto min_var = std::min({1, 2, 3, 4, 5}); // fast
auto vec = std::vector<int>{1, 2, 3, 4, 5};
min_var = std::min({vec[0], vec[1], vec[2], vec[3], vec[4]}); // slow

[1]: N2722 p. 2

like image 34
mfuchs Avatar answered Nov 25 '22 13:11

mfuchs