chuckj
chuckj

Reputation: 155

Construct returned object in calling function's scope

Is it possible to force C++ to construct an object in the scope of a calling function? What I mean is to explicitly do what an return value optimization (RVO) does.

I have some container classes which are in a chain of derivation. Since the classes are constructed with stack data, they can't be returned, so I disabled the copy constructor and assignment operators. For each class, I am providing an iterator. The constructor of each iterator has only one argument: a pointer to the container class. To get the iterator, I want to use this function:

BindPackIterator BindPack.begin(void)
{
    return BindPackIterator(this);
}

in this context:

for (auto i=bindpack.begin(); !i.end(); ++i) { i.run(); }

The compiler issues errors, complaining about not being able to copy the BindPackIterator object. Remember, I disabled them.

What I want to happen is for the BindPackIterator to be instantiated in the calling function's scope to avoid either a copy or move operation.

In this particular case, I know I can do a workaround, changing the begin function to return a BindPack pointer,

for(BindPackIterator i=bindpack.begin(); !i.end(); ++i) { i.run(); }

and I've experimented a bit, without success, with decltype and this construction:

auto BindPack::begin(void) -> BindPackIterator
{
    return BindPackIterator(this);
}

This is just the example with which I'm currently frustrated. There have been other projects where the obvious solution is for the function to instantiate an object in the calling function's scope. The move constructor (foo&&) helps in some cases, but for objects with many data members, even that can be inefficient. Is there a design pattern that allows object construction/instantiation in the caller's scope?

Upvotes: 3

Views: 153

Answers (2)

chuckj
chuckj

Reputation: 155

Is it fair to say that the answer is "No," it is not possible to construct a returned object in the calling function's scope? Or in other words, you can't explicitly tell the compiler to use RVO.

To be sure, it is a dangerous possibility: stack memory used to construct the object while available in the called function will not be valid in the calling function, even though the values might remain untouched in the abandoned stack frame. This would result in unpredictable behavior.

Upon further consideration, while summing up at the end of this response, I realized that the compiler may not be able to accurately predict the necessary stack size for objects created in the calling function and initialized in a called function, and it would not be possible to dynamically expand the stack frame if the execution had passed to another function. These considerations make my whole idea impossible.

That said, I want to address the workarounds that solve my iterator example.

I had to abandon the idea of using auto like this:

for (auto i=bindpack.begin(); !i.end(); ++i)

Having abandoned auto, and realizing that it's more sensible to explicitly name the variable anyway (if the iterator is different enough to require a new class, it's better to name it to avoid confusion) , I am using this constructor:

BindPackIterator(BindPack &ref) : m_ref_pack(ref), m_index(0) { }

in order to be able to write:

for (BindPackIterator i=bindpack; !i.end(); ++i)

preferring to initialize with an assignment. I used to do this when I was last heavily using C++ in the late 1990's, but it's not been working for me recently. The compiler would ask for a copy operator I didn't want to define for reasons stated above. Now I think that problem was due to my collection of constructors and assignment operators I define to pass the -Weffc++ test. Using simplified classes for this example allowed it to work.

Another workaround for an object more complicated than an iterator might be to use a tuple for the constructor argument for objects that need multiple variables to initialize. There could be a casting operator that returns the necessary tuple from the class that initializes the object.

The constructor could look like:

FancyObject(BigHairyTuple val) : m_data1(get<0>(val)), m_data2(get<1>(val), etc

and the contributing object would define this:

class Foo
{
    ...
    operator BigHairyTuple(void) {
        return BigHairyTuple(val1, val2, ...);
    }
};

to allow:

FancyObject fo = foo;

I haven't tested this specific example, but I'm working with something similar and it seems likely to work, with some possible minor refinements.

Upvotes: 0

Chris Drew
Chris Drew

Reputation: 15334

Putting n.m.'s comment into code, write a constructor for BindPackIterator that takes a BindPack and initializes the iterator in the "begin" state. e.g:

BindPackIterator(BindPack* pack) : pack(pack), pos(0){ }

That you can use in your for loop:

BindPack pack;

for(BindPackIterator i(&pack); !i.end(); ++i){
  i.run();
}

Live demo

Upvotes: 1

Related Questions