1

Consider these two functions:

int foo(std::array<int, 10> const& v) {
    auto const w = v;
    int s{};
    for (int i = 0; i < v.size(); ++i) {
        s += (i % 2 == 0 ? v : w)[i];
    }
    return s;
}

int bar(std::array<int, 10> const& v) {
    return std::accumulate(v.begin(),
                           v.end(),
                           0,
                           std::plus{});
}

They do exactly the same thing, if not for the fact that foo does it awkwardly, by making a const copy of argument v, and then picking elements alternatively from v and from the copy w.

Clang can see through that, and generates the same assembly for the two functions (GCC and MSVC miss this optimization).

If I change std::array<int, 10> to std::vector<int> throughout, though, the two assembly outputs differ; specifically, I can see the calls to new+memcpy and delete performed in foo, corresponding to w initialization at auto const w = v; and destruction at } respectively.

I assume that difference in the assembly maps to foo and bar behaving differently exception-wise, in that the allocation caused by auto const w = v; could fail in foo, whereas there's no allocation going on in bar.

However, is the compiler required to emit code for foo that is different from bar's?

Or could the compiler do as it does for the std::array case, i.e. work out that "if the allocation at auto const w = v; succeeds, then the execution of the code is the same as if w was changed to v, so I might as well make this simplification and even prevent any exception at all"?


Incidentally, I've also tried another case of "useless copy that does not require allocation",

int foo(std::optional<int> const& v, bool b) {
    auto const w = v;
    return (b ? v : w).value();
}

int bar(std::optional<int> const& v) {
    return v.value();
}

and Clang generates the same assembly (except for using test in foo and cmp in bar to check if the optional has_value, but that's a non-functional difference).

1 Answer 1

9

From as-if rule since C++14

new expression has another exception from the as-if rule: the compiler may remove calls to the replaceable allocation functions even if a user-defined replacement is provided and has observable side-effects.

So if the allocator of your vector use a new expression, then the optimization can be done.

Default allocator doesn't use a new expression but the replaceable allocation directly, but according to std::allocate's notes

The "unspecified when and how" wording makes it possible to combine or optimize away heap allocations made by the standard library containers, even though such optimizations are disallowed for direct calls to ::operator new. For example, this is implemented by libc++

So the optimization can be done in that case too.

Sign up to request clarification or add additional context in comments.

5 Comments

Side question: why are they called replaceable allocation functions rather than overloadable allocation functions? Is it just equivalent wording?
User might write a function with same signature to replace default implementation by the new one. overloads use different signatures.
Can you explain a bit more why "Default allocator doesn't use a replaceable allocation function"? If its ::allocate uses ::operator new... isn't that the same as "using a replaceable allocation function"?
I'll read through the two links at the end of "implemented by libc++".
Fixed wording. new expression (i.e new T) is subject to optimization, but not a direct call to operator new(..).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.