What's the purpose of const swap() function?

Viewed 1649

While implementing a custom tuple (here), I found there is a wired swap() function that takes const parameters (cppreference):

template< class... Types >
constexpr void swap( const std::tuple<Types...>& lhs,
                     const std::tuple<Types...>& rhs ) noexcept(/* see below */);

and a const-qualified swap() member function (cppreference):

constexpr void swap( const tuple& other ) noexcept(/* see below */) const;

const means the object is read-only, but to swap two objects, it has to modify the objects, which violates the const-ness.

So, What's the purpose of const swap() function?

3 Answers

This was introduced in the "zip" proposal P2321 originally described in "A Plan for C++23 Ranges" P2214.

P2321

  • swap for const tuple and const pair. Once tuples of references are made const-assignable, the default std::swap can be called for const tuples of references. However, that triple-move swap does the wrong thing:

    int i = 1, j = 2;
    const auto t1 = std::tie(i), t2 = std::tie(j);
    
    // If std::swap(t1, t2); called the default triple-move std::swap then
    // this would do
    auto tmp = std::move(t1);
    t1 = std::move(t2);
    t2 = std::move(tmp);
    
    // i == 2, j == 2
    

    This paper therefore proposes adding overloads of swap for const tuples and pairs to correctly perform element-wise swap.

P2214 explains why const assignability is needed for the implementation of zip. It stems from assignment operators not being ref qualified.

You have missed the footnote about when that overload is available:

This overload participates in overload resolution only if std::is_swappable_v<const Ti> is true for all i from 0 to sizeof...(Types).

If you have a type const_swappable such that swap(const const_swappable &, const const_swappable &) is sensible, then there is no reason why you shouldn't be able to swap const std::tuple<const_swappable> &.

As an example, consider a pointer-like type, that can swap the values of the pointee without modifying the pointer:

#include <type_traits>
#include <iostream>

struct foo {
    int * x;
};

void swap(const foo& a, const foo& b){
    std::swap(*a.x,*b.x);
};

int main(){
    int a = 42;
    int b = 3;

    foo f1{&a};
    foo f2{&b};

    swap(f1,f2);

    std::cout << "foo is const swappable: " << std::is_swappable_v<const foo> << "\n";
    std::cout << *f1.x << "\n";
    std::cout << *f2.x << "\n";

}

And note from cppreference:

  1. The program is ill-formed if (std::is_swappable_v<const Types> && ...) is not true.

That is: You can only const swap the tuples if the types in the tuple can be const swapped.

Related