Home > Enterprise >  Elide copy/move when wrapping a function receiving a prvalue
Elide copy/move when wrapping a function receiving a prvalue

Time:08-31

I am trying to create a wrapper function that has the exact same interface as the function that it wraps, with zero runtime cost overhead.

In the example code below, is it possible to design my_function_wrapped in a way so that it has the same interface as my_function, so that calling it with the exact same arguments as to my_function always yield the same results?

#include <iostream>

using namespace std;

struct SMyStruct
{
    SMyStruct() { cout << "SMyStruct constructed" << std::endl;}
    SMyStruct(SMyStruct&& Other) { cout << "SMyStruct moved" << std::endl;}
    SMyStruct(const SMyStruct& Other) { cout << "SMyStruct copied" << std::endl; }
    ~SMyStruct() {cout << "SMyStruct destroyed" << std::endl;}
};

void my_function(SMyStruct Arg)
{
}

template<typename T>
void my_function_wrapped(T&& Arg)
{
    my_function(std::forward<T>(Arg));

    // Some extra logic here that doesn't use Arg
}

int main()
{
    cout << "-----------------------------" << endl;
    cout << "Direct call to my_function:" << endl;
    cout << "-----------------------------" << endl;
    
    my_function(SMyStruct());

    cout << "-----------------------------" << endl;
    cout << "Wrapped call to my_function:" << endl;
    cout << "-----------------------------" << endl;

    my_function_wrapped(SMyStruct());

    return 0;
}

This program outputs:

-----------------------------
Direct call to my_function:
-----------------------------
SMyStruct constructed
SMyStruct destroyed
-----------------------------
Wrapped call to my_function:
-----------------------------
SMyStruct constructed
SMyStruct moved
SMyStruct destroyed
SMyStruct destroyed

I realize that copy/move is elided on the first call to my_function because SMyStruct() is a prvalue. Is it possible to wrap this call in my_function_wrapped and still get the elided copy/move? Is there any zero-cost way to abstract away the call?

godbolt-link to the code: https://godbolt.org/z/joGTTe64f

Thanks!

CodePudding user response:

No, it is not possible to chain copy elision of prvalues through function calls like this.

Copy elision only works because the caller can construct the function parameter knowing where it needs to place it from the declaration of the function and the calling convention used.

The original caller doesn't know that you are going to simply forward the argument to another function in the body and therefore it cannot know that it is supposed to construct the object into the deeper stack frame. C is also designed in such a way that functions can be compiled individually only having to know the declarations of other functions (constant expression evaluation aside). Definitions of the functions don't even have to be available where a call happens.

Allowing this would require some additional language feature to annotate a function declaration to inform callers where they have to construct the parameter and I think it would be difficult to find a good specification for such a feature.

What you can do is pass the arguments for your constructor, or more generally a callable object which creates your prvalue, around, e.g.

template<typename F>
void my_function_wrapped(F&& f)
{
    my_function(std::invoke(std::forward<F>(f)));
}

//...

my_function_wrapped([]{ return SMyStruct(); });

The lambda can capture arguments to the constructor if needed.

(Note however that all of this requires C 17. You also tagged C 14, but in C 14 there was no guaranteed copy elision in any of the situations under discussion anyway.)

CodePudding user response:

It depends on your real case (or if there is a real case to begin with), though in your example there is really no point in passing the SMyStruct to the wrapper to forward it to the actual function (because SMyStruct has no state). If instead you forward parameters for the constructor you get desired output:

template<typename...T>
void my_function_wrapped(T&&... Arg)
{
    my_function(SMyStruct(std::forward<T>(Arg)...));
}

Live Demo

  • Related