3

Given a function which returns a heavy-to-construct and heavy-to-destroy object, is there a way to skip object construction and destruction if the function return value is not consumed?

HeavyObject func();
auto res = func(); // heavy object is constructed and returned
func(); // lightweight "null" object is returned and immediately destroyed

Is there a technique which I can use except tag dispatching like void func(NotConsumed);?

Update

It is okay to discard the return value, so [[nodiscard]] is not an option. Clients can decide if they want to use the result or not.

Let's assume func() initiates some job and HeavyObject serves as a some sort of handle. Clients can use it later to control/monitor the job, if they want to. Construction of such handle could involve e.g. creating pipes or heap memory allocation.

There are other overloads of func() where the return value must be consumed and which are marked as [[nodiscard]]. The new overload does not have this requirement but must be compatible with existing API.

Kane
  • 5,595
  • 1
  • 18
  • 27
  • First of all copying (and copy-construction) might be *elided*. Secondly you could always *move* "heavy" objects. And compilers are good at optimization... Build with optimization enabled and look at the generated assembly code to see if such an object is created or not. – Some programmer dude Jul 28 '21 at 10:25
  • 3
    You could also use `[[nodiscard]]` attribute to produce warning if heavy object is discarded. – Ari0nhh Jul 28 '21 at 10:26
  • 4
    What else does `func()` do? Does it have some other side-effects as well as producing the heavy object? (i.e. I'm leading up to "can you separate the functionality?") – BoBTFish Jul 28 '21 at 10:28
  • you can return an object with lazy construction – engf-010 Jul 28 '21 at 10:30
  • 2
    I would say that `func` is doing two much at once, because it seems like it does somethign and in addition it optionally creates and returns an object, but your question suggests that those are two seperate concerns – 463035818_is_not_an_ai Jul 28 '21 at 10:34
  • see also [How to force a compile error in C++(17) if a function return value isn't checked? Ideally through the type system](https://stackoverflow.com/q/68462879/995714) – phuclv Jul 28 '21 at 10:48
  • Building on the comment by @463035818_is_not_a_number, perhaps you should create the `HeavyObject` instance somewhere else, and pass it by reference into the function as an argument? – Some programmer dude Jul 28 '21 at 12:11

1 Answers1

6

Since C++17 there is the nodiscard attribute. However, this will only help to not ignore the returned value and the compiler is only encouraged to issue a warning.

I don't think there is a simple answer for what you want (ie no second function or overload). Also because the construction of the heavy object takes place already inside the function, not after it returned.

What you can do is to return a proxy that only when needed constructs the heavy object. Something along the line of :

struct heavy_object { int x; };

struct proxy {
    int parameter_needed_to_construct_heavy_object;
    operator heavy_object() {
        return {parameter_needed_to_construct_heavy_object};
    }
};

proxy func() {
    return {42};
}

int main() {
    func(); // ok, no heavy object is constructed
    heavy_object ho = func(); // ok, heavy object is constructed
    auto x = func(); // "ok-ish", caller must be aware that x is just the proxy
}

Instead of an implicit converions a get() could take its place, but that wont change the fact that the caller must do something extra to get the actual return value.

Having said that, I think using two seperate functions or an overload as you propse is the simpler and cleaner solution.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • I'm not sure, but this code will probably decrease use of move semantics, isn't it? because the heavy object will be saved into class member. – Afshin Jul 28 '21 at 10:41
  • @Afshin I dont understand what you mean. No `heavy_object` is stored as member. Maybe `operator heavy_object` needs to be tweaked to make use of rerturn value optimization, i am not fluent with that, but at least I am certain that it isnt much different from returning the `heavy_object` from `func` directly – 463035818_is_not_an_ai Jul 28 '21 at 10:46