There is already the post sorting vector of pointers but this is not about a vector of pointers but rather about a vector of referencing pointers.
3 integer are put into a std::vector<int*>
which is than sorted according to the values behind the pointers.
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
int a = 3;
int b = 2;
int c = 1;
std::vector<int*> vec;
vec.emplace_back(&a);
vec.emplace_back(&b);
vec.emplace_back(&c);
std::sort(vec.begin(), vec.end(), [](const int* a, const int* b) {
return *a < *b;
});
std::cout << "vec = " << *vec[0] << ", " << *vec[1] << ", " << *vec[2] << '\n';
std::cout << "abc = " << a << ", " << b << ", " << c << '\n';
}
however, it seems that only the vector was sorted as the output shows:
vec = 1, 2, 3
abc = 3, 2, 1
I think the reason is that std::sort()
, while comparing correctly, simply assigns adresses instead of the values. What is wrong here? Why can't I sort this vector of pointed-to values?
The next part is rather TL,DR as it shows my approach to solve this. An easy task that reveals itself to be rather frustratingly complicated. @Bathsheba's answer points out that it is not possible . So the next parts, initially considered to be the presentation of my attempt, might now be considered to be the reason why it is not possible.
My idea is to make a pointer class wrapper to provide my own contructors and assignement operators. std::sort()
behaves differently if the size of the container is small ( <= 32
on my system) but in both cases there are assignements and moves happening - as this small snippet from the _Insertion_sort_unchecked
(from <algorithm>
) function shows.
( _BidIt
== std::vector<int*>::iterator
and _Iter_value_t<_BidIt>
== int*
)
_BidIt _Insertion_sort_unchecked(_BidIt _First, const _BidIt _Last, _Pr _Pred)
{ // insertion sort [_First, _Last), using _Pred
if (_First != _Last)
{
for (_BidIt _Next = _First; ++_Next != _Last; )
{ // order next element
_BidIt _Next1 = _Next;
_Iter_value_t<_BidIt> _Val = _STD move(*_Next);
if (_DEBUG_LT_PRED(_Pred, _Val, *_First))
{ // found new earliest element, move to front
_Move_backward_unchecked(_First, _Next, ++_Next1);
*_First = _STD move(_Val);
Let's make a class assignement_pointer
that behaves like a pointer except that it assigns the values instead of the adresses.
template<typename T>
class assignement_pointer {
public:
assignement_pointer(T& value) {
this->m_ptr = &value;
std::cout << "<T>& constructor\n";
}
assignement_pointer(const assignement_pointer& other) {
this->m_ptr = other.m_ptr;
std::cout << "copy constructor\n";
}
assignement_pointer(assignement_pointer&& other) {
std::cout << "move assignement constructor >> into >> ";
*this = std::move(other);
}
assignement_pointer& operator=(const assignement_pointer& other) {
*this->m_ptr = *other.m_ptr;
std::cout << "copy assignement operator\n";
return *this;
}
assignement_pointer& operator=(assignement_pointer&& other) {
std::swap(this->m_ptr, other.m_ptr);
std::cout << "move assignement operator\n";
return *this;
}
T& operator*() {
return *this->m_ptr;
}
const T& operator*() const {
return *this->m_ptr;
}
private:
T* m_ptr;
};
As you can see there are also temporary std::cout
's to see which constructors / assignement operators were called while going through std::sort()
in the main:
///...
std::vector<assignement_pointer<int>> vec;
vec.reserve(3);
vec.emplace_back(assignement_pointer(a));
vec.emplace_back(assignement_pointer(b));
vec.emplace_back(assignement_pointer(c));
std::cout << "\nsort()\n";
std::sort(vec.begin(), vec.end(), [](const assignement_pointer<int>& a, const assignement_pointer<int>& b) {
return *a < *b;
});
std::cout << "\nvec = " << *vec[0] << ", " << *vec[1] << ", " << *vec[2] << '\n';
std::cout << "abc = " << a << ", " << b << ", " << c << '\n';
giving the output:
<T>& constructor
move assignement constructor >> into >> move assignement operator
<T>& constructor
move assignement constructor >> into >> move assignement operator
<T>& constructor
move assignement constructor >> into >> move assignement operator
sort()
move assignement constructor >> into >> move assignement operator
move assignement operator
move assignement operator
move assignement constructor >> into >> move assignement operator
move assignement operator
move assignement operator
move assignement operator
vec = 1, 2, 3
abc = 3, 2, 1
std::sort()
calls only move functions. vec
is sorted but not a
, b
, c
the last point makes sense, because since only move functions are called the copy assignement operator assignement_pointer& operator=(const assignement_pointer& other);
(which does the value assignement) is never called. The unnecessary copy constructor and assignement operator can be removed:
template<typename T>
class assignement_pointer {
public:
assignement_pointer(T& value) {
this->m_ptr = &value;
}
assignement_pointer(const assignement_pointer& other) = delete;
assignement_pointer& operator=(const assignement_pointer& other) = delete;
assignement_pointer(assignement_pointer&& other) {
std::cout << "move assignement constructor >> into >> ";
*this = std::move(other);
}
assignement_pointer& operator=(assignement_pointer&& other) {
std::swap(this->m_ptr, other.m_ptr);
std::cout << "move assignement operator\n";
return *this;
}
T& operator*() {
return *this->m_ptr;
}
const T& operator*() const {
return *this->m_ptr;
}
private:
T* m_ptr;
};
Now std::sort()
inner processes are rather complicated but in the end it comes down to failing at an operation like std::swap()
:
int main() {
int a = 3;
int b = 2;
std::vector<assignement_pointer<int>> vec;
vec.reserve(2); //to avoid re-allocations
vec.emplace_back(assignement_pointer(a));
vec.emplace_back(assignement_pointer(b));
std::cout << "swap()\n";
assignement_pointer<int> ptr_a{ a };
assignement_pointer<int> ptr_b{ b };
std::swap(ptr_a, ptr_b);
std::cout << "\nptrs = " << *ptr_a << ", " << *ptr_b << '\n';
std::cout << "a, b = " << a << ", " << b << '\n';
}
and as this output shows:
move assignement constructor >> into >> move assignement operator
move assignement constructor >> into >> move assignement operator
swap()
move assignement constructor >> into >> move assignement operator
move assignement operator
move assignement operator
ptrs = 2, 3
a, b = 3, 2
it's that sitation that only the pointers are switched but not the original variables. std::swap
is basically
_Ty _Tmp = _STD move(_Left);
_Left = _STD move(_Right);
_Right = _STD move(_Tmp);
explaining the
move assignement constructor >> into >> move assignement operator
move assignement operator
move assignement operator
the move assignement operator simply swaps the pointers so creating a temporary variable doesn't do anything. I see two possible solutions to this:
swap()
for the classbut both don't work.
m_ptr
from this->
class is always nullptr
and I would rather not dereference this.std::sort()
never uses std::swap()
but instead just std::move()
s from all over the place. (as already partially seen by _Insertion_sort_unchecked
). You'll need to roll your own sort function to do this.
The callback lambda is used to evaluate the ordering, but you need to tweak the part that does the actual exchange of elements: and the C++ standard library sort
does not support your doing that.
Fortunately a quick sort without any bells and whistles (such as pre-randomisation) comes out in a few tens of lines, so this is not a particularly onerous task to complete.
Just retain a copy of the original vector of pointers and copy the sorted values over:
std::vector<int*> original = vec; // Do this before the std::sort
Then after you print a,b,c:
std::vector<int> xfer;
for (auto ptr : vec) {
xfer.push_back(*ptr);
}
auto it = std::begin(xfer);
for (auto ptr : original) {
*ptr = *it++;
}
std::cout << "abc = " << a << ", " << b << ", " << c << '\n';
Output:
abc = 1, 2, 3
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.