Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I avoid copying during the intialization of a std::initializer_list without using raw pointers?

Let's say I have several objects declared locally that I want to iterate over using range-based for syntax. This seems to work well, however, it appears that to put the local objects into the initializer_list, a copy is performed. This is bad news for objects like std::shared_ptr for which (as I understand) incrementing the reference count is an atomic operation. The only way I think this can be avoided is by using raw pointers.

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> ptrInt1 = std::make_shared<int>(1);
    std::shared_ptr<int> ptrInt2 = std::make_shared<int>(2);
    /* in this loop, ptrInt1 and ptrInt2 are copied before they are binded
       to ptrInt, this is ugly since the reference counter needs to temporarily
       increased */
    for(const std::shared_ptr<int>& ptrInt : {ptrInt1, ptrInt2}) {
        std::cerr << *ptrInt << std::endl;
    }
    /* this solution works, but it feels somewhat ugly having to convert my smart
       pointers to raw pointers to avoid the copying, perhaps there is a better
       solution ?? */
    for(const int* rawPtrInt : {ptrInt1.get(), ptrInt2.get()}) {
        std::cerr << *rawPtrInt << std::endl;
    }
    return 0;
}

Is there a way to iterate over a group of locally declared objects without copying them or resorting to using raw pointers?

like image 439
mallwright Avatar asked Dec 23 '22 00:12

mallwright


2 Answers

You can use std::ref to build a list of std::reference_wrappers. This hides the pointer and lets you write the list like

for(const std::shared_ptr<int>& ptrInt : {std::ref(ptrInt1), std::ref(ptrInt2)}) {
    std::cerr << *ptrInt << std::endl;
}
like image 196
NathanOliver Avatar answered Jan 22 '23 13:01

NathanOliver


Here's a small function template that extends @NathanOliver's answer and reduces some of the typing.

#include <array>
#include <functional>

// No rvalues, thanks to @NathanOliver for pointing that out:
template <class ...T>
auto crefRange(const T&&...) = delete;

template <class ...T>
auto crefRange(const T&... args)
{
   using First = std::tuple_element_t<0, std::tuple<T...>>;

   return std::array<First, sizeof...(T)>{{std::cref(args)...}};
}

You can instantiate and call it via

for(const std::shared_ptr<int>& ptrInt : crefRange(ptrInt1, ptrInt2))
    std::cerr << *ptrInt << std::endl;

This will fail if you instantiate it with different types, but this limitation is identical with the std::initializer_list approach.

like image 26
lubgr Avatar answered Jan 22 '23 15:01

lubgr