Consider the following example (Godbolt):
#include <vector>
#include <iostream>
#include <ranges>
#include <algorithm>
struct A
{
A() {}
A( const A& ) { std::cout << "Copy\n"; }
A( A&& ) noexcept { std::cout << "Move\n"; }
A& operator=(const A&) { std::cout << "Copy assigned\n"; return *this; }
A& operator=( A&& ) noexcept { std::cout << "Move assigned\n"; return *this; }
int x = 10;
};
int main()
{
std::vector<A> vec( 10 );
std::cout << "Init\n";
std::cout << std::ranges::max( vec, [] ( const auto& a, const auto& b ) { std::cout << "cmp" << std::endl; return a.x < b.x; } ).x;
}
This program compiled with GCC 13.2 (even with -O3
optimization turned on) produces the following output:
Init
Copy
Copy
cmp
Copy
cmp
Copy
cmp
Copy
cmp
Copy
cmp
Copy
cmp
Copy
cmp
Copy
cmp
Copy
cmp
10
But compiled with Clang 17 (with -stdlib=libc++
and any optimization level), it performs no copying at all (except for the returned value, as I understand it):
Init
cmp
cmp
cmp
cmp
cmp
cmp
cmp
cmp
cmp
Copy
10
If A
has a costly copy-constructor, this difference will drastically decrease performance.
Is there a reason why GCC has this implementation of std::ranges::max
or is it a bug?
It's, what I presume, a "bug" in the gcc implementation and I wrote a bugreport.
LLVM has two versions inside the
operator()
overload being used:.. but it doesn't matter if I disable the version currently being used and instead use the
while
loop. It still doesn't copy.Now for the
operator()
overload in GCC's case:The copy is here:
I assume it should be:
because with that change, it doesn't copy anymore.
Note: I've added
std::
andstd::ranges::
in a couple of places to be able to use the algorithms outside their natural habitat which is inside the standard library implementations.Update
The bug is now confirmed. Jonathan Wakely also replied:
So, if his initial assessment is correct, it should be a low hanging fruit for someone to pick and we can hope for a fix in a near release.