0

In the following code I have a function foo taking as argument a std::function returning a big object (here a vector) and uses it only to access its values. The big object may be built on-the-fly by fun or be a reference to an existing object as the two lambda expressions in the main show.

I would like to optimize this code to avoid useless copies possibly by choosing the right return type of the std::function.

If I use return by value, RVO optimizes out the copy and makes exactly what I want for the "build on-the-fly" case: no copies are done, the vector is built exactly where it needs to and later destroyed at the end of foo. However, in the second case the compiler does copy the vector even if I don't really need it. The second case would be the perfect situation for a return-by-reference but this would break the first case since reference to temporary are evil.

I understand that making foo directly taking a reference to the vector would solve the problem in this particular case but my use case is more complex. Is there a way to solve this?

#include <vector>
#include <iostream>
#include <functional>

struct myVec : public std::vector<double> {
    using std::vector<double>::vector;
    myVec(myVec const & v){
        std::cout << "Copy ctor\n";
        *this=v;
    }
};


void foo(std::function<myVec(void)> fun){
    for(auto & v : fun()){
        std::cout << v << " ";
    }
    std::cout<<std::endl;
}

int main() {
    foo([]()->myVec{return myVec(100,0.);});
    myVec existing(100,1.);
    foo([&existing]()->myVec{return existing;});
    return 0;
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
Teloze
  • 279
  • 2
  • 8
  • 1
    "reference to temporary are evil" -- why? What happens if you make a move ctor for `myVec`? – Jonathan Lam Jul 31 '19 at 23:56
  • Just to clarify, this line: `foo([]()->myVec{return myVec(100,0.);});` is currently unoptimized, and `foo([&existing]()->myVec{return existing;});` (w/ lvalue reference) is? – Jonathan Lam Jul 31 '19 at 23:56
  • 2
    The compiler will not optimize away anything until the compiler can prove that the optimization has no observable side effects. I see multiple reasons why the compiler might find it difficult to do so. It may be obvious to you and me, but we have a human brain, and the compiler does not. – Sam Varshavchik Aug 01 '19 at 00:02
  • Do you need a `vector` or maybe just a `tuple`? – tadman Aug 01 '19 at 00:03
  • @JonathanLam I think the move ctor is inherited from std::vector and this is the reason why no copy is done in the first case. For the second case, how would I manage the rvalue reference? I don't want "existing" to be moved, just to be referenced. Concerning the second comment, the problem is opposite. The line ``` foo([&existing]()->myVec{return existing;}); ``` copies the vector in foo. – Teloze Aug 01 '19 at 00:18
  • @tadman vector is just an exemple of possibly "Big object". This may be a complex class containing multiple members – Teloze Aug 01 '19 at 00:20
  • @Teloze I might have misunderstood. Try making the lambda expression return an lvalue reference? And so you're saying that the rvalue one is already being moved, not copied? – Jonathan Lam Aug 01 '19 at 00:21
  • @JonathanLam If I make lvalue reference, then the first case will break because the temporary object is created within ```fun``` and immediately destroyed. – Teloze Aug 01 '19 at 00:28
  • Fair enough. In situations with a trivial number of members `std::tuple` may perform better. If this is some kind of large, expensive to copy object, use references if not one of the [pointer-wrappers](https://en.cppreference.com/w/cpp/memory). – tadman Aug 01 '19 at 00:37

1 Answers1

1

[&existing]()->myVec{return existing;} return by copy, not by reference, so it would create copy.

You need so [&existing]()->myVec&{return existing;}.

Then issue is with std::function

so either provide 2 overloads (void foo(std::function<myVec()>) and void foo(std::function<myVec&()>)), or use template and get rid of std::function:

template <typename F>
void foo(F fun){
    for (const auto & v : fun()){
        std::cout << v << " ";
    }
    std::cout<<std::endl;
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Oh, that makes sense. So a simple overload with the lvalue-returning lambda would do it! – Jonathan Lam Aug 01 '19 at 00:33
  • The code I have is not really using ```std::function```. I just put it to make the code shorter. However, for testing, I already tried the overloading you suggest but the compiler is complaining about an ambiguous call to overloaded foo. Anyway, I really do like your templated version! – Teloze Aug 01 '19 at 00:44