A proposal to add swap traits to the standard library

423 views
Skip to first unread message

Andrew C. Morrow

unread,
Feb 2, 2013, 11:37:04 AM2/2/13
to std-pr...@isocpp.org

Hi all -

I'd like to propose the addition of two new type relationship traits to the C++ standard library:

template<typename T, typename U = T>
struct std::is_swappable;

template<typename T, typename U = T>
struct std::is_nothrow_swappable;

The purpose of these two new traits is primarily to make it easier to correctly query the noexcept status of swap when writing a noexcept expression.

A draft of the proposal can be found here: http://acmorrow.github.com/swap_traits/nXXXX.html

This is my first proposal, so it may need some work yet. I'd appreciate your feedback and suggestions.

Thanks for your time,
Andrew

Ville Voutilainen

unread,
Feb 2, 2013, 12:53:16 PM2/2/13
to std-pr...@isocpp.org

----- Original message -----
> Hi all -
>
> I'd like to propose the addition of two new type relationship traits to
> the C++ standard library:
>
> template<typename T, typename U = T>
> struct std::is_swappable;
>
> template<typename T, typename U = T>
> struct std::is_nothrow_swappable;
>
> The purpose of these two new traits is primarily to make it easier to
> correctly query the noexcept status of swap when writing a noexcept
> expression.

Just quickly, what's the issue with
noexcept(swap(x, y)) ?

Howard Hinnant

unread,
Feb 2, 2013, 1:00:55 PM2/2/13
to std-pr...@isocpp.org
Challenge: Make this code compile without a using directive or using declaration:

#include <algorithm>

template <class T>
void
foo(T& x, T& y) noexcept(noexcept(swap(x, y)));

struct A
{
A() = default;
A(const A&);
A& operator=(const A&);
};

void swap(A&, A&) noexcept;

int main()
{
int i;
A a;
static_assert(noexcept(foo(i, i)), "");
static_assert(noexcept(foo(a, a)), "");
}

Howard

Ville Voutilainen

unread,
Feb 2, 2013, 2:12:33 PM2/2/13
to std-pr...@isocpp.org
On 2 February 2013 20:00, Howard Hinnant <howard....@gmail.com> wrote:
> On Feb 2, 2013, at 12:53 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:
>> Just quickly, what's the issue with
>> noexcept(swap(x, y)) ?
> Challenge: Make this code compile without a using directive or using declaration:

Wow. I think "wtf". Utterly baffled. :) I didn't mean to say that this
proposal is
unnecessary, but your example seems to indicate it's definitely worth
considering.
In other words, I tried it quickly, and with all the expertise I have,
I don't want
to try further. Perhaps I'm missing something, but why doesn't it work?

Mikael Kilpeläinen

unread,
Feb 2, 2013, 2:20:44 PM2/2/13
to std-pr...@isocpp.org
Because you are calling the swap with ints.. so no ADL to find the
std::swap .. you can make it compile by having


void swap(int& a, int& b) noexcept {
std::swap(a, b);
}

before the foo.. Or am i totally mistaken here?


Mikael

Howard Hinnant

unread,
Feb 2, 2013, 2:24:06 PM2/2/13
to std-pr...@isocpp.org
Here is what the body of foo will presumably look like:

template <class T>
void
foo(T& x, T& y) noexcept(noexcept(swap(x, y)))
{
using std::swap;
swap(x, y);
}

I.e. we need to call swap unqualified to pick up A's swap, but std::swap needs to be in scope to swap ints. I don't see a way to put the logic of the body of foo into foo's noexcept spec without polluting foo's namespace with swap.

Here might be another way to code it:

template <class T>
void
foo(T& x, T& y) noexcept(noexcept((using std::swap, swap(x, y))))
{
using std::swap;
swap(x, y);
}

but my compiler gets grumpy when I try this.

I did finally get it to work this way:

#include <algorithm>

namespace details
{

using std::swap;

template <class T>
void
foo_imp(T& x, T& y) noexcept(noexcept(swap(x, y)))
{
swap(x, y);
}

}

template <class T>
void
foo(T& x, T& y) noexcept(noexcept(details::foo_imp(x, y)))
{
details::foo_imp(x, y);
}

struct A
{
A() = default;
A(const A&);
A& operator=(const A&);
};

void swap(A&, A&) noexcept;

int main()
{
int i;
A a;
static_assert(noexcept(foo(i, i)), "");
static_assert(noexcept(foo(a, a)), "");
}

Yay! I got the correct noexcept spec without polluting my namespace with std::swap. I've also come close to just implementing the proposed std::is_nothrow_swappable, except that I failed to make it reusable. ;-) It only works for foo. :-\

Howard

Ville Voutilainen

unread,
Feb 2, 2013, 2:55:03 PM2/2/13
to std-pr...@isocpp.org
On 2 February 2013 21:24, Howard Hinnant <howard....@gmail.com> wrote:
> Yay! I got the correct noexcept spec without polluting my namespace with std::swap. I've also come close to just implementing the proposed std::is_nothrow_swappable, except that I failed to make it reusable. ;-) It only works for foo. :-\

No offense, but I have a litmus test; it says "if it takes Howard 50
lines to do and he claims it was 'not that
hard', there's a usability issue around." :D

Zhihao Yuan

unread,
Feb 2, 2013, 3:15:36 PM2/2/13
to std-pr...@isocpp.org
On Sat, Feb 2, 2013 at 1:24 PM, Howard Hinnant <howard....@gmail.com> wrote:
> foo(T& x, T& y) noexcept(noexcept((using std::swap, swap(x, y))))

I agree "is_nothrow_swappable" is a missing point. But,
like the imaginary code above, I think the problem is on
ADL, not swap itself.

For short, I want a `using` directive for a function, only, like
a function level `try` block.

void f(T& x, T& y) using std::swap noexcept(noexcept(swap(x, y)))

So that directive also works for the _trailing return types_.

Multiple `using`s should also be supported,

using namespace std, namespace boost
using std::cout, std::swap

or

using namespace std using namespace boost
using std::cout using std::swap

Whatever you like.


--
Zhihao Yuan, ID lichray
The best way to predict the future is to invent it.
___________________________________________________
4BSD -- http://4bsd.biz/

Jeffrey Yasskin

unread,
Feb 3, 2013, 12:52:24 AM2/3/13
to std-pr...@isocpp.org, Dave Abrahams
:)

At one point, I think Dave suggested making ::std::begin(foo)
automatically delegate to ADL when that's possible. I forget exactly
how that worked, but if it's possible, we should consider doing it for
swap() as well.

Jeffrey

Vicente J. Botet Escriba

unread,
Feb 3, 2013, 3:00:06 AM2/3/13
to std-pr...@isocpp.org
Le 03/02/13 06:52, Jeffrey Yasskin a �crit :
The following code based on the idea of boost::swap works with clang 3.2

"The template function boost::swap allows the values of two variables to
be swapped, using argument dependent lookup to select a specialized swap
function if available. If no specialized swap function is available,
std::swap is used."

namespace nstd_swap_impl
{
using std::swap;

template <class T>
void swap_impl(T& x, T& y) noexcept(noexcept(swap(x, y)))
{
swap(x, y);
}
}

namespace nstd
{
template<class T1, class T2>
void swap(T1& x, T2& y) noexcept(noexcept(::nstd_swap_impl::swap_impl(x, y)))
{
::nstd_swap_impl::swap_impl(x, y);
}
}

template <class T>
void
foo(T& x, T& y) noexcept(noexcept(nstd::swap(x, y)));

struct A
{
A() = default;
A(const A&);
A& operator=(const A&);
};

void swap(A&, A&) noexcept;

int main()
{
int i;
A a;
static_assert(noexcept(foo(i, i)), "");
static_assert(noexcept(foo(a, a)), "");
}

I don't know if Dave suggestion was related to this technique, but at least the nstd::swap is reusable.

Vicente

P.S. I needed to rename swap on for make working gcc

namespace nstd
{
template<class T1, class T2>
void swap2(T1& x, T2& y) noexcept(noexcept(::nstd_swap_impl::swap_impl(x, y)))
{
::nstd_swap_impl::swap_impl(x, y);
}
}

template <class T>
void
foo(T& x, T& y) noexcept(noexcept(nstd::swap2(x, y)));


as otherwise

../example/swap_test.cpp:19:77: error: template instantiation depth exceeds maximum of 128 (use -ftemplate-depth= to increase the maximum) substituting �template<class T> void nstd_swap_impl::swap_impl(T&, T&) [with T = <missing>]�
void swap(T1& x, T2& y) noexcept(noexcept(::nstd_swap_impl::swap_impl(x, y)))
^
../example/swap_test.cpp:19:77: required from �void nstd::swap(T1&, T2&) [with T1 = int; T2 = int]�
../example/swap_test.cpp:27:50: required from �void nstd_swap_impl::swap_impl(T&, T&) [with T = int]�
../example/swap_test.cpp:19:77: required from �void nstd::swap(T1&, T2&) [with T1 = int; T2 = int]�
../example/swap_test.cpp:27:50: required from �void nstd_swap_impl::swap_impl(T&, T&) [with T = int]�
../example/swap_test.cpp:19:77: required from �void nstd::swap(T1&, T2&) [with T1 = int; T2 = int]�
../example/swap_test.cpp:27:50: [ skipping 118 instantiation contexts, use -ftemplate-backtrace-limit=0 to disable ]
../example/swap_test.cpp:27:50: required from �void nstd_swap_impl::swap_impl(T&, T&) [with T = int]�
../example/swap_test.cpp:19:77: required from �void nstd::swap(T1&, T2&) [with T1 = int; T2 = int]�
../example/swap_test.cpp:27:50: required from �void nstd_swap_impl::swap_impl(T&, T&) [with T = int]�
../example/swap_test.cpp:19:77: required from �void nstd::swap(T1&, T2&) [with T1 = int; T2 = int]�
../example/swap_test.cpp:27:50: required from �void foo(T&, T&) [with T = int]�
../example/swap_test.cpp:42:36: required from here


Howard Hinnant

unread,
Feb 3, 2013, 2:16:28 PM2/3/13
to std-pr...@isocpp.org
This gave me the idea that a simple solution would be to just slap a noexcept on std::iter_swap and use that:

#include <algorithm>

template <class T>
void
foo(T& x, T& y) noexcept(noexcept(std::iter_swap(&x, &y)))
{
std::iter_swap(&x, &y);
}

struct A
{
A() = default;
A(const A&);
A& operator=(const A&);
};

void swap(A&, A&) noexcept;

int main()
{
int i;
A a;
static_assert(noexcept(foo(i, i)), "");
static_assert(noexcept(foo(a, a)), "");
}

std::iter_swap (if it has a noexcept spec) serves the same purpose as Vicente's nstd::swap above.

Howard

Mikael Kilpeläinen

unread,
Feb 3, 2013, 3:27:10 PM2/3/13
to std-pr...@isocpp.org
3.2.2013 20:16, Howard Hinnant kirjoitti:
> This gave me the idea that a simple solution would be to just slap a
> noexcept on std::iter_swap and use that: #include <algorithm> template
> <class T> void foo(T& x, T& y) noexcept(noexcept(std::iter_swap(&x,
> &y))) { std::iter_swap(&x, &y); } struct A { A() = default; A(const
> A&); A& operator=(const A&); }; void swap(A&, A&) noexcept; int main()
> { int i; A a; static_assert(noexcept(foo(i, i)), "");
> static_assert(noexcept(foo(a, a)), ""); } std::iter_swap (if it has a
> noexcept spec) serves the same purpose as Vicente's nstd::swap above.

Nice idea, however std::iter_swap does not have noexcept specifier.. How
about using something else for the same job.. like say, tuple:

template<typename T>
void foo(T&, T&) noexcept(noexcept( std::tuple<T>().swap(
std::declval<std::tuple<T>&>() )));

Not quite as nice but maybe it works.


Mikael



Zhihao Yuan

unread,
Feb 3, 2013, 3:46:36 PM2/3/13
to std-pr...@isocpp.org
On Sat, Feb 2, 2013 at 11:52 PM, Jeffrey Yasskin <jyas...@googlers.com> wrote:
> At one point, I think Dave suggested making ::std::begin(foo)
> automatically delegate to ADL when that's possible. I forget exactly
> how that worked, but if it's possible, we should consider doing it for
> swap() as well.

Can you give me a link? I did not find it...

Mikael Kilpeläinen

unread,
Feb 4, 2013, 12:23:54 AM2/4/13
to std-pr...@isocpp.org
3.2.2013 21:27, Mikael Kilpel�inen kirjoitti:
>
> template<typename T>
> void foo(T&, T&) noexcept(noexcept( std::tuple<T>().swap(
> std::declval<std::tuple<T>&>() )));
>
>
Seems i was already sleeping when i wrote that, as obviously this
imposes some restrictions for T but maybe the concept works as a dirty
workaround.

So lets summarise most of the thread :

- Problem exists with free functions and lookup if wanted to use in
noexcept, trailing return
- Having is_swappable traits (as proposed) fixes it for one of the most
common cases, and i do agree it feels like those type traits are missing.
- We might be able to fix this for most std free functions by making
them first to perform ADL like pointed out by Yasskin. I wonder how many
swap relies on calling the std::swap directly though and possibly
recursing then.

The problem itself it generic, but is it worth "fixing"? Is there much
similar usage with other than std library that would benefit from it?


Mikael

Nikolay Ivchenkov

unread,
Feb 5, 2013, 5:22:02 PM2/5/13
to std-pr...@isocpp.org
On Saturday, February 2, 2013 8:37:04 PM UTC+4, Andrew Morrow wrote:
A draft of the proposal can be found here: http://acmorrow.github.com/swap_traits/nXXXX.html

1. IMO, the expression should be swap(declval<T>(), declval<U>()) rather than swap(declval<T&>(), declval<U&>()), so we could test lvalues and rvalues.

2. The wording "The set of swaps considered for resolution must include std::swap as well as swap overloads available via argument dependent lookup" isn't precise enough, at least because there are several templates with name 'swap' in std.

3. An implementation of is_swappable and is_nothrow_swappable could be more simple:

namespace std
{
    template <bool _B>
        using __bool_constant = integral_constant<bool, _B>;

    template <class _T, class _U, class = void>
        struct __is_swappable_test : false_type
    {
        using __nothrow_swappable = false_type;
    };

    template <class _T, class _U>
        struct __is_swappable_test<_T, _U,
            decltype((void)swap(declval<_T>(), declval<_U>()))> : true_type
    {
        using __nothrow_swappable =
            __bool_constant<noexcept(swap(declval<_T>(), declval<_U>()))>;
    };

    template <class _T, class _U = _T>
        struct is_swappable :
            __is_swappable_test<_T, _U> {};
    template <class _T, class _U = _T>
        struct is_nothrow_swappable :
            __is_swappable_test<_T, _U>::__nothrow_swappable {};

}


On Sunday, February 3, 2013 12:15:36 AM UTC+4, Zhihao Yuan wrote:
But,
like the imaginary code above, I think the problem is on
ADL, not swap itself.

For short, I want a `using` directive for a function, only, like
a function level `try` block.

void f(T& x, T& y) using std::swap noexcept(noexcept(swap(x, y)))

IMO, such code looks terrible. I would prefer noexcept(auto) instead.

Mikael Kilpeläinen

unread,
Feb 6, 2013, 4:51:57 AM2/6/13
to std-pr...@isocpp.org
5.2.2013 23:22, Nikolay Ivchenkov kirjoitti:
> 1. IMO, the expression should be swap(declval<T>(), declval<U>())
> rather than swap(declval<T&>(), declval<U&>()), so we could test
> lvalues and rvalues.
>
So that in the common case you need to use is_swappable<A&> ? What is
the use for swap functions taking rvalues?
As the normal swap taking lvalues you cannot call with declval<T>() when
T is not reference.


Mikael

Andrzej Krzemieński

unread,
Feb 6, 2013, 5:34:34 AM2/6/13
to std-pr...@isocpp.org


W dniu poniedziałek, 4 lutego 2013 06:23:54 UTC+1 użytkownik Mikael Kilpeläinen napisał:

If the problem is primarily about std::swap, and swap is so very special, perhaps defining or aliasing it in the global namespace would solve the problem?

Nikolay Ivchenkov

unread,
Feb 6, 2013, 8:47:57 AM2/6/13
to std-pr...@isocpp.org
On Wednesday, February 6, 2013 1:51:57 PM UTC+4, Mikael Kilpeläinen wrote:
5.2.2013 23:22, Nikolay Ivchenkov kirjoitti:
> 1. IMO, the expression should be swap(declval<T>(), declval<U>())
> rather than swap(declval<T&>(), declval<U&>()), so we could test
> lvalues and rvalues.
>
So that in the common case you need to use is_swappable<A&> ?

I don't know what common case you have in mind. I can imagine an application like this:

    is_swappable<decltype(*declval<dereferenceable_type>())>

where the result of the dereferencing could be a prvalue of a proxy type.

Nikolay Ivchenkov

unread,
Feb 6, 2013, 9:14:31 AM2/6/13
to std-pr...@isocpp.org
On Wednesday, February 6, 2013 2:22:02 AM UTC+4, Nikolay Ivchenkov wrote:

2. The wording "The set of swaps considered for resolution must include std::swap as well as swap overloads available via argument dependent lookup" isn't precise enough, at least because there are several templates with name 'swap' in std.

3. An implementation of is_swappable and is_nothrow_swappable could be more simple:

I forgot to say that we have to constrain std::swap defined in <utility> then:

    template <class T> auto
        swap(T &a, T &b) noexcept(
                is_nothrow_move_constructible<T>{} &&
                is_nothrow_move_assignable<T>{})
            -> typename std::enable_if
                <
                    is_move_constructible<T>{} && is_move_assignable<T>{}
                >::type;

otherwise our is_swappable would be useless:

    int const x = 1;
    using type = decltype(std::swap(x, x));

Here the unevaluated call to std::swap is well-formed, while x is not swappable.

Mikael Kilpeläinen

unread,
Feb 6, 2013, 10:01:52 AM2/6/13
to std-pr...@isocpp.org
6.2.2013 14:47, Nikolay Ivchenkov kirjoitti:
On Wednesday, February 6, 2013 1:51:57 PM UTC+4, Mikael Kilpeläinen wrote:
5.2.2013 23:22, Nikolay Ivchenkov kirjoitti:
> 1. IMO, the expression should be swap(declval<T>(), declval<U>())
> rather than swap(declval<T&>(), declval<U&>()), so we could test
> lvalues and rvalues.
>
So that in the common case you need to use is_swappable<A&> ?

I don't know what common case you have in mind. I can imagine an application like this:

My "common" case..

template<typename T>
void foo( T& ) noexcept( is_nothrow_swappable<T> );

which would require is_nothrow_swappable<T&> to pick up swap(T&, T&).


    is_swappable<decltype(*declval<dereferenceable_type>())>

where the result of the dereferencing could be a prvalue of a proxy type.
I see, so the point is to interoperate with decltype. For me this proxy case seems rather special but valid point.
I don't think people usually use swap like that though (to have swap for rvalue references, or is it?) as it makes harder to
write generic code with specialised swaps. I am all for make it more general but is it surprising? Is it easy to make mistake
and forget the & for example?

I have to say more I think about it, the more I am turning to want what you said.
Mostly because what does swappable type mean? Is the lvalue of it swappable or rvalue or something else?
One option would be to have special case for the plain type..

Btw, 17.6.3.2/2 has the symmetry contraint for swappable .. swap(t, u) and swap(u, t) must be valid. Should we consider this as well?
But I guess that is not quite so important.


Mikael
Message has been deleted

Howard Hinnant

unread,
Feb 8, 2013, 6:36:03 PM2/8/13
to std-pr...@isocpp.org
On Feb 6, 2013, at 11:32 AM, Nikolay Ivchenkov <ts...@mail.ru> wrote:

>> I see, so the point is to interoperate with decltype. For me this proxy case seems rather special but valid point.
>> I don't think people usually use swap like that though (to have swap for rvalue references, or is it?) as it makes harder to
>> write generic code with specialised swaps.
>>
> Well, cases where left and right arguments for swap have different types are also rare, but the proposal tends to support such rare applications, right?

For me here is the poster-child for heterogeneous swap:

#include <vector>
#include <iostream>

int main()
{
std::vector<bool> v(1);
bool b = true;
swap(b, v[0]);
std::cout << v[0] << '\n';
}

The standard doesn't say this should work, but imho the standard is broken in that regard. It works in libc++ just as a personal expression of defiance. :-) Not sure if/where else.

That being said, if generic code were trying to tell if code like this was going to work, I would expect it to look like:

std::is_swappable<bool>::value

or

std::is_swappable<std::vector<bool>::value_type>::value

or

std::is_swappable<std::vector<bool>::reference>::value

I don't know of a case where a heterogeneous swap is useful for being explicitly heterogeneous. It is usually useful as trying to masquerade as a homogeneous swap.

Howard

Mikael Kilpeläinen

unread,
Feb 8, 2013, 7:11:48 PM2/8/13
to std-pr...@isocpp.org
6.2.2013 17:32, Nikolay Ivchenkov kirjoitti:

which would require is_nothrow_swappable<T&> to pick up swap(T&, T&).

 I don't see problems with is_nothrow_swappable<T&>.

First i want to say that i am sorry, I got your respond now.. gotta love gmail.

Yes, indeed, there is no problem, as long as you don't expect it to be without &


Well, cases where left and right arguments for swap have different types are also rare, but the proposal tends to support such rare applications, right?
Indeed, it does, and that is good thing. First i had the idea of making it simple as possible, but you already convinced me that should not be the case,
and this is just part of it.


Is the definition of std::is_assignable surprising? How often do we use rvalues as the left operand of assignment operator? Wouldn't different conventions about interpretation of template arguments for std::is_assignable and std::is_swappable be surprising for people?
I don't think it is valid argument to find something as surprising in the standard.. yes, we have many surprising things, we ought to minimise those. But like said earlier,
it is not quite that easy, and maybe having the generic solution just benefits more of us.


I have to say more I think about it, the more I am turning to want what you said.
Mostly because what does swappable type mean? Is the lvalue of it swappable or rvalue or something else?

There may be different definitions.
Indeed, and that is the point. Your way seems to make least confusion, at least after little thought.. as it applies to the exact "expression" you are giving.

 
One option would be to have special case for the plain type..

Do you mean something like this?

    // general version
    template <class T, class U = T>
        struct is_swappable_with;

    template <class T>
        struct is_swappable :
            is_swappable_with
            <
                typename add_lvalue_reference<T>::type,
                typename add_lvalue_reference<T>::type
            > {};
 
I think, it would be acceptable.

Yes, that is along the lines i was suggesting...

Btw, 17.6.3.2/2 has the symmetry contraint for swappable .. swap(t, u) and swap(u, t) must be valid. Should we consider this as well?

Perhaps.

Perhaps indeed, on the other hand you might want to see if the exact expression is okey.. but then again the _swappable_ seems to be something else in standard.
So it might be conflicting.


Mikael



Mikael Kilpeläinen

unread,
Feb 8, 2013, 7:29:47 PM2/8/13
to std-pr...@isocpp.org
9.2.2013 0:36, Howard Hinnant kirjoitti:
> For me here is the poster-child for heterogeneous swap:
>
> #include <vector>
> #include <iostream>
>
> int main()
> {
> std::vector<bool> v(1);
> bool b = true;
> swap(b, v[0]);
> std::cout << v[0] << '\n';
> }
>
> The standard doesn't say this should work, but imho the standard is broken in that regard. It works in libc++ just as a personal expression of defiance. :-) Not sure if/where else.
I agree something like this should work, but vector<bool> is rather
special anyhow. The fix seems quite trivial for this specific case
though, did you have some more generic
problem in mind which i just failed to see?

>
> I don't know of a case where a heterogeneous swap is useful for being explicitly heterogeneous. It is usually useful as trying to masquerade as a homogeneous swap.
>
I think so too. Can you think of other "common" examples than vector<bool>?


Mikael

Tony V E

unread,
Feb 8, 2013, 11:43:43 PM2/8/13
to std-pr...@isocpp.org
If I can write (or similar):

T t;
U u;
// swap them:
T tmp = t;
t = u;
u = tmp; // or std::move(tmp)

Then shouldn't I be able to swap them?

Tony
Message has been deleted

Howard Hinnant

unread,
Feb 9, 2013, 11:15:29 AM2/9/13
to std-pr...@isocpp.org
Yes, but only if T and U are the same type or if you've written a custom swap for T and U. The latter is how the swap(bool&, vector<bool>::reference) example works.

Howard

Andrew C. Morrow

unread,
Feb 9, 2013, 1:52:08 PM2/9/13
to std-pr...@isocpp.org

Thank you everyone for your comments, and I apologize for the week long delay getting back to this thread. I'm going to try to summarize a bit and I have several questions as well.

- As many pointed out, the underlying problem here is with ADL, not with swap. If 'noexcept(auto)', or Jeffrey's thought to give special lookup semantics to swap (or even all free functions in std), or some sort of 'using expression' were available, then std::is_swappable and std::is_nothrow_swappable would be unnecessary. However, a language level change will take some time. Is swap important enough to warrant special handling in the short term via a library extension, even if the value of is_[nothrow_]swappable is undermined by future language changes?

- declval<T> vs. declval<T&>: I think the argument in favor of declval<T> was convincing to me. It does place some burden on the user , but I'd imagine users of the type traits facilities are able to deal with that burden. Unless there is a strong objection I will update my proposal along these lines.

- Nikolay's comment on wording: I agree the wording is not sufficiently precise, but I'm not familiar enough with the idioms of the standard to do much better on my own so I did not attempt to perfect it. Suggestions on how to improve wording anywhere it is weak in the proposal would be much appreciated.

- Nikolay's alternative implementation: I like the compactness of it, but the required changes to std::swap makes it less appealing to me. I do think writing the proposed implementation as if it were within ::std is a good idea, as it may reveal other problems, so I plan to update my proposal similarly.

- Heterogeneous swap: My original implementation only handled homogeneous swap. I added support for heterogeneous swap because of 17.6.3.2 paragraph 1 which sets up "swappable with" in terms of heterogeneous types. However, 20.2 only specifies homogeneous swap. Howard's example with vector<bool> seems to make a case for supporting heterogeneous swap though. I'm happy to follow guidance here from those who understand this area better.

- Symmetry for the heterogeneous case, assuming it is retained: If I'm understanding correctly, I think the symmetry constraint can be enforced easily for is_swappable: (ignoring possible declval<T> vs declval<T&> changes here): 

In is_swappable_test:

            using test_type_tu = decltype(test<T, U>(std::declval<T&>(), std::declval<U&>()));
            using test_type_ut = decltype(test<U, T>(std::declval<U&>(), std::declval<T&>()));

        public:
            static constexpr bool value =
                !std::is_same<test_type_tu, swap_not_found_type>::value &&
                !std::is_same<test_type_ut, swap_not_found_type>::value;

The proposal language would need to be updated to capture the symmetry requirement as well but that seems straightforward.

I don't think any changes need to be made to is_nothrow_swappable: I don't see that the standard says anything about the symmetrical overloads that make types "swappable" sharing a noexcept status, so for that case I think the argument order should be considered as provided by the caller. Admittedly it seems strange to have one overload be noexcept and the other not.

- Overall is there a general thought on whether this proposal has enough merit to continue refining it?

Thanks,
Andrew





--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/?hl=en.



Nikolay Ivchenkov

unread,
Feb 9, 2013, 5:13:14 PM2/9/13
to std-pr...@isocpp.org
On Saturday, February 9, 2013 10:52:08 PM UTC+4, Andrew Morrow wrote:
- Nikolay's comment on wording: I agree the wording is not sufficiently precise, but I'm not familiar enough with the idioms of the standard to do much better on my own so I did not attempt to perfect it. Suggestions on how to improve wording anywhere it is weak in the proposal would be much appreciated.

First, I think that Table 49 would be more appropriate place for describing is_swappable an is_nothrow_swappable.
You can specify conditions and preconditions similarly to std::is_assignable and std::is_nothrow_assignable (see N3485 - § 20.9.4.3 - Table 49). For is_swappable trait the expression declval<T>() = declval<U>() would be replaced with two expressions swap(declval<T>(), declval<U>()) and swap(declval<U>(), declval<T>()), with the following addition:

    The context in which the aforementioned expressions are considered shall ensure that a candidate set for "swap" consists of the two swap function templates defined in <utility> (20.2) and the lookup set produced by argument-dependent lookup (3.4.2).

- Nikolay's alternative implementation: I like the compactness of it,

It could be much more compact if we would have a proper core language support:

    is_well_formed ( expression )
    is_well_formed ( type-id )
    is_well_formed compound-statement

    is_nothrow_well_formed ( expression )
    is_nothrow_well_formed compound-statement

-----------------------------------------------------------
namespace std
{
    template <bool C>
        using bool_constant = integral_constant<bool, C>;


    template <class T, class U>
        struct is_swappable :
            bool_constant
            <
                is_well_formed(swap(declval<T>(), declval<U>())) &&
                is_well_formed(swap(declval<U>(), declval<T>()))

            >

    template <class T, class U>
        struct is_nothrow_swappable :
            bool_constant
            <
                is_swappable<T, U>{} &&
                is_nothrow_well_formed(swap(declval<T>(), declval<U>()))
            >

    template <class T, class... Params>
        struct is_constructible :
            bool_constant
            <
                is_well_formed{ T t(declval<Params>()...); }
            >
    {};

    template <class T, class... Params>
        struct is_nothrow_constructible :
            bool_constant
            <
                is_nothrow_well_formed{ T t(declval<Params>()...); }
            >
    {};


    template <class T, class U>
        struct is_affined :
            is_same<T const volatile, U const volatile> {};

    template <class From, class To>
        struct is_nothrow_convertible :
            bool_constant
            <
                is_affined<From, void>{} && is_affined<To, void>{} ||
                is_nothrow_well_formed
                {
                    void test(To) noexcept;
                    (test)(declval<From>());
                }
            > {};
}
-----------------------------------------------------------
 
but the required changes to std::swap makes it less appealing to me.

I don't see any solution that would be correct and more simple.

Andrew C. Morrow

unread,
Mar 3, 2013, 9:48:02 PM3/3/13
to std-pr...@isocpp.org
Based on the feedback and discussion in this thread, I have updated my proposal for the addition of std::is_swappable and std::is_nothrow_swappable as follows:

- Changed from declval<T&> to declval<T>, and for U.
- Added the swap(t,u) swap(u, t) symmetry requirement for is_swappable.
- Switched from "type relationship" to "type properties"
- Proposed additions are now to Table 49, rather than table 51.
- Adopted Nikolay's proposed wording for table 49.
- Updated the example implementation as if it were actually being written in 'namespace std' for a standard library header implementation.

The updated draft can be found here: http://acmorrow.github.com/swap_traits/nXXXX.html


--

Mikael Kilpeläinen

unread,
Mar 4, 2013, 4:55:12 AM3/4/13
to std-pr...@isocpp.org, Andrew C. Morrow

Based on the feedback and discussion in this thread, I have updated my proposal for the addition of std::is_swappable and std::is_nothrow_swappable as follows:

- Changed from declval<T&> to declval<T>, and for U.
- Added the swap(t,u) swap(u, t) symmetry requirement for is_swappable.
- Switched from "type relationship" to "type properties"
- Proposed additions are now to Table 49, rather than table 51.
- Adopted Nikolay's proposed wording for table 49.
- Updated the example implementation as if it were actually being written in 'namespace std' for a standard library header implementation.

The updated draft can be found here: http://acmorrow.github.com/swap_traits/nXXXX.html

Looking good, I will take closer look when I have time but small remark now..


"The context in which the aforementioned expressions are considered shall ensure that a candidate set for swap consists of the two swap function templates defined in <utility> (20.2) and the lookup set produced by argument-dependent lookup (3.4.2)."

I assume this means one has to explicitly include <utility> when ever these traits are used.
Then the wording seems bit  vague though, how do I ensure those swap functions will be candidates if I don't know how it is implemented?
I would rather see something like "if the header <utility> is not included prior to a use of these traits the program is ill-formed".

Another option would be to specify that the new header includes <utility> which i think is fine, in which case that needs to be added to the synopsis.


Mikael

Andrew C. Morrow

unread,
Mar 6, 2013, 10:36:00 AM3/6/13
to Mikael Kilpeläinen, std-pr...@isocpp.org



On Mon, Mar 4, 2013 at 4:55 AM, Mikael Kilpeläinen <mikael.ki...@gmail.com> wrote:

Looking good, I will take closer look when I have time but small remark now..


"The context in which the aforementioned expressions are considered shall ensure that a candidate set for swap consists of the two swap function templates defined in <utility> (20.2) and the lookup set produced by argument-dependent lookup (3.4.2)."

I assume this means one has to explicitly include <utility> when ever these traits are used.
Then the wording seems bit  vague though, how do I ensure those swap functions will be candidates if I don't know how it is implemented?
I would rather see something like "if the header <utility> is not included prior to a use of these traits the program is ill-formed".

Another option would be to specify that the new header includes <utility> which i think is fine, in which case that needs to be added to the synopsis.


Mikael


Thanks Mikael.

Your suggestion to just specify that <utility> is included by the new header seems better to me. The alternative just introduces yet another way to write an ill-formed program. I've updated the <swap_traits> synopsis accordingly.

While doing so I realized that some of the section references, names, and titles were made incorrect after changing from Table 51 to Table 49, so I've fixed those up too.


Andrew

Mikael Kilpeläinen

unread,
Mar 6, 2013, 12:19:17 PM3/6/13
to Andrew C. Morrow, std-pr...@isocpp.org

>
> http://acmorrow.github.com/swap_traits/nXXXX.html
>
a few more comments.

In the introduction:
"The former will derive from std::true_type if the expression
'swap(declval<T&>(), declval<U&>())' resolves"
You should remove the & like other places, also this is not exactly true
any more because of the symmetry requirement.

Constructors/Public Data:
- Do you need to say these here as the UnaryTypeTrait already sets
requirements. It seems those requirements might not
be exactly the same.

Alternative Designs:
"in the noexcept oeprator would"
Just a typo.

You forgot to update the heading as 20.9.4.3 is "Type properties" and
not "Relationships between types".

It seems to me that the is_nothrow_swappable should have the same
symmetry requirement.


Mikael

Andrew C. Morrow

unread,
Mar 10, 2013, 3:49:21 PM3/10/13
to Mikael Kilpeläinen, std-pr...@isocpp.org


On Wed, Mar 6, 2013 at 12:19 PM, Mikael Kilpeläinen <mikael.ki...@gmail.com> wrote:
>
>>
>> http://acmorrow.github.com/swap_traits/nXXXX.html
>>
> a few more comments.

Thanks! Replies inline below, and a new draft has been pushed.

>
> In the introduction:
> "The former will derive from std::true_type if the expression
> 'swap(declval<T&>(), declval<U&>())' resolves"
> You should remove the & like other places, also this is not exactly true any
> more because of the symmetry requirement.

Done, I think.

>
> Constructors/Public Data:
> - Do you need to say these here as the UnaryTypeTrait already sets
> requirements. It seems those requirements might not
> be exactly the same.

Agreed it is redundant. I've removed it.

>
> Alternative Designs:
> "in the noexcept oeprator would"
> Just a typo.

Done.

>
> You forgot to update the heading as 20.9.4.3 is "Type properties" and not
> "Relationships between types".

Done.

>
> It seems to me that the is_nothrow_swappable should have the same symmetry
> requirement.

I think I disagree. I don't see anything in 17.6.3.2 that requires two swap implementations forming a heterogeneous swap pair to share a noexcept status:

class Foo {};
class Bar {};

void swap(Foo& f, Bar& b) noexcept {
  // ...
}

void swap(Bar& b, Foo& f) {
  // ...
}

So is_swappable<Foo, Bar>::value and is_swappable<Bar, Foo>::value should clearly both be true_type, along with is_nothrow_swappable<Foo, Bar>::value, but I think is_nothrow_swappable<Bar, Foo>::value should still be false_type.

Perhaps it would make sense to add noexcept status to the symmetry constraints of 17.6.3.2, and then is_nothrow_swappable could be tightened up, but given what is there now I don't think the symmetry requirement which applies to std::is_swappable
propagates to std::is_nothrow_swappable.

Mikael Kilpeläinen

unread,
Mar 10, 2013, 6:08:43 PM3/10/13
to Andrew C. Morrow, std-pr...@isocpp.org
10.3.2013 20:49, Andrew C. Morrow kirjoitti:


I think I disagree. I don't see anything in 17.6.3.2 that requires two swap implementations forming a heterogeneous swap pair to share a noexcept status:

class Foo {};
class Bar {};

void swap(Foo& f, Bar& b) noexcept {
  // ...
}

void swap(Bar& b, Foo& f) {
  // ...
}

So is_swappable<Foo, Bar>::value and is_swappable<Bar, Foo>::value should clearly both be true_type, along with is_nothrow_swappable<Foo, Bar>::value, but I think is_nothrow_swappable<Bar, Foo>::value should still be false_type.

Right, it just feels weird since the swappable itself has symmetry constraint. Also since the symmetry constraint says the two swapping expressions
produce the same outcome it would imply so, not forcing the noexcept though. I am not sure how to interpret the 17.6.3.2/2

"An object t is swappable with an object u if and only if:
    — the expressions swap(t, u) and swap(u, t) are valid when evaluated in the context described below,
    and
    — these expressions have the following effects:
        — the object referred to by t has the value originally held by u and
        — the object referred to by u has the value originally held by t."

Now if I throw exception the last two sentences are not true any more. So one could even read this that no exceptions can be thrown which seems overly tight
and was not meant most likely. Am I missing something here?

Would having is_value_swappable make sense for convenience (per 17.6.3.2/5)?


Mikael

Andrew C. Morrow

unread,
Mar 11, 2013, 10:55:41 AM3/11/13
to Mikael Kilpeläinen, std-pr...@isocpp.org
On Sun, Mar 10, 2013 at 6:08 PM, Mikael Kilpeläinen <mikael.ki...@gmail.com> wrote:

"An object t is swappable with an object u if and only if:
    — the expressions swap(t, u) and swap(u, t) are valid when evaluated in the context described below,
    and
    — these expressions have the following effects:
        — the object referred to by t has the value originally held by u and
        — the object referred to by u has the value originally held by t."

Now if I throw exception the last two sentences are not true any more. So one could even read this that no exceptions can be thrown which seems overly tight
and was not meant most likely. Am I missing something here?

That is an interesting point: it does seem like the language here forbids throwing swaps, but I think that must be unintentional, or that we are misreading it, or that another section provides an escape. See 20.2.2, which defines the noexcept status of the default std::swap template, which would be pointless if swap were meant to be forbidden from throwing. So perhaps the language in 17.6.3.2 needs to be clarified, but that is independent of my proposal. I suppose if it were determined that throwing swaps were in fact forbidden that is_nothrow_swappable would become useless.
 

Would having is_value_swappable make sense for convenience (per 17.6.3.2/5)?

Perhaps, but I think this proposal as written is close to complete (and thank you for your help getting it there), so I'm not sure I want to open another front. I saw in another post that the deadline for submission to the LWG chair for the next mailing is this week, and I'd like to make that deadline if possible. Do you think the current draft is sufficiently polished to submit?

Mikael Kilpeläinen

unread,
Mar 11, 2013, 6:12:07 PM3/11/13
to Andrew C. Morrow, std-pr...@isocpp.org
11.3.2013 15:55, Andrew C. Morrow kirjoitti:


That is an interesting point: it does seem like the language here forbids throwing swaps, but I think that must be unintentional, or that we are misreading it, or that another section provides an escape. See 20.2.2, which defines the noexcept status of the default std::swap template, which would be pointless if swap were meant to be forbidden from throwing. So perhaps the language in 17.6.3.2 needs to be clarified, but that is independent of my proposal. I suppose if it were determined that throwing swaps were in fact forbidden that is_nothrow_swappable would become useless.
Yes, it is independent, just something I noticed :)

�

Would having is_value_swappable make sense for convenience (per 17.6.3.2/5)?

Perhaps, but I think this proposal as written is close to complete (and thank you for your help getting it there), so I'm not sure I want to open another front. I saw in another post that the deadline for submission to the LWG chair for the next mailing is this week, and I'd like to make that deadline if possible. Do you think the current draft is sufficiently polished to submit?

I agree. I think the draft is good enough to be submitted. The deadline is friday.


Mikael

Nikolay Ivchenkov

unread,
Mar 12, 2013, 5:55:58 PM3/12/13
to std-pr...@isocpp.org
I still don't see any useful applications of such is_swappable. It doesn't answer whether the provided argument(s) is/are swappable, even roughly. A call to the general unconstrained version of std::swap, where both arguments are lvalues of the same type, will be always well-formed. If you don't want to change the declaration of std::swap, you can introduce some constrained surrogate for it and construct the candidate set from the surrogate and versions found by ADL:

    namespace impl
    {
        // constrained surrogate
        template <class T>
            typename enable_if

                <
                    is_move_constructible<T>{} && is_move_assignable<T>{}
                >::type
            swap(T &, T &) noexcept(
                is_nothrow_move_constructible<T>{} &&
                is_nothrow_move_assignable<T>{});

        DEF_EXPRESSION_PATTERN(
            pattern_swap,
            (class T, class U),
            swap(declval<T>(), declval<U>()));
    }
    template <class T, class U = T>
       struct is_swappable :
           is_valid_expression<impl::pattern_swap, T, U> {};
    template <class T, class U = T>
       struct is_nothrow_swappable :
           is_valid_nothrow_expression<impl::pattern_swap, T, U> {};

http://liveworkspace.org/code/3jUdM0$0

Nikolay Ivchenkov

unread,
Mar 12, 2013, 6:22:37 PM3/12/13
to std-pr...@isocpp.org
On Wednesday, March 13, 2013 1:55:58 AM UTC+4, Nikolay Ivchenkov wrote:
I still don't see any useful applications of such is_swappable. It doesn't answer whether the provided argument(s) is/are swappable, even roughly. A call to the general unconstrained version of std::swap, where both arguments are lvalues of the same type, will be always well-formed. If you don't want to change the declaration of std::swap, you can introduce some constrained surrogate for it and construct the candidate set from the surrogate and versions found by ADL:

Sorry, I didn't consider this idea properly. Such implementation isn't correct, because the general std::swap can conflict with such surrogate (e.g. we will get value false on std::string *&). Any other surrogate, that would be less specialized than the general std::swap, can potentially conflict with a user-defined template. So, I see only one viable solution: the general std::swap should be constrained.

Andrew C. Morrow

unread,
Mar 12, 2013, 8:16:46 PM3/12/13
to std-pr...@isocpp.org

--
 

You raise a good point. I think I had not really grasped what you were saying the first time. You are correct that is_swappable is flawed in that it will sometimes return true_type, even though the swap expression wouldn't actually compile, unless std::swap is constrained as well. Your 'y1' type in your linked example demonstrates that. I actually hadn't noticed while experimenting with it because libc++ does in fact constrain its swap.

However, I still need something that tells me "the expression 'swap(a,b)' is legal when unevaluated", because I need to avoid building that expression if it is illegal when I am trying to query for the noexcept status in the implementation of is_nothrow_swappable. And I think there is agreement that is_nothrow_swappable is useful. Also, is_swappable does capture some useful information, such as the symmetry requirement for heterogeneous swap.

But clearly this is bad in the case where swap is not constrained:

#include <swap_traits>

struct X {
  X& operator=(X const&) = delete;
};

static if (std::is_swappable<X>::value) {
  X x1, x2;
  using std::swap;
  // may confusingly fail to compile if swap is unconstrained
  swap(x1, x2);
}

Would renaming it to something with weaker implications improve the situation?

std::is_swap_expressible<T, U=T>?
std::has_swap_overloads<T, U=T>?

Another option would be to eliminate std::is_swappable entirely and only retain std::is_nothrow_swappable, which is the true goal of the proposal anyway.

Also, while working with your test code, I realized there was an issue in my example implementation. My version of std::is_swappable<int, int> (note the lack of '&') derived from true_type, rather than false_type as it should, due to:

    template<typename __V1, typename __V2>
    static auto __test(__V1 __v1, __V2 __v2) -> decltype(swap(__v1, __v2));

I believe the correct implementation is

    template<typename __V1, typename __V2>
    static auto __test(__V1&& __v1, __V2&& __v2) -> decltype(swap(std::forward<__V1>(__v1), std::forward<__V2>(__v2)));

I've pushed an update to the proposal with this change. Latest revision is here:


Given that the submission deadline is Friday, any thoughts on how to resolve the issues with std::is_swappable or comments on the above change to the example implementation would be much appreciated. 


Andrew C. Morrow

unread,
Mar 14, 2013, 3:50:33 PM3/14/13
to std-pr...@isocpp.org

Mikael and Nikolay -

Any suggestions about how to repair this proposal given the problems with is_swappable?

Mikael Kilpeläinen

unread,
Mar 14, 2013, 4:54:34 PM3/14/13
to std-pr...@isocpp.org, Andrew C. Morrow
14.3.2013 20:50, Andrew C. Morrow kirjoitti:
>
> Any suggestions about how to repair this proposal given the problems
> with is_swappable?
>

Not really. Sorry.

I was experimenting with a version that has swap(...) that hides the
std::swap to see if there is anything that matches via
ADL and if not, use qualified std::swap and some more constraints. This
works in normal cases fine, but
of course there is this small gap between the template std::swap and the
swap(...) where the adl version might match
and hence you get wrong results again (you cannot hide the std::swap
with same signature as it is as it gets pulled in by adl anyhow
and it is ambigious, like Nikolay already noted).

So I can only conclude, since you need the is_swappable for the main
purpose, namely is_swappale_nothrow. Keep it there, explain
the current problems with it and mention that something should probably
be done about it (ie. constrain swap or ..) and let committee give feedback.
And make sure that the nothrow version is the main purpose of this proposal.

Of course we should continue thinking how to solve this but...
Mikael

Andrzej Krzemieński

unread,
Mar 15, 2013, 3:59:24 AM3/15/13
to std-pr...@isocpp.org, Andrew C. Morrow

Sorry, if my suggestion is dumb (I am not entirely following the details), but it looked like you have a problem with implementing the proposed traits within the C++ language, rather than with the proposal itself. Did you consider proposing a requirement that is not implementable within C++ but could be provided by the vendors by some intrinsics or compiler magic (much like other traits)?

Regards,
&rzej  

Andrew C. Morrow

unread,
Mar 15, 2013, 11:26:15 AM3/15/13
to Andrzej Krzemieński, std-pr...@isocpp.org
No, I don't think it is dumb at all. I've adopted your suggestion, included in a section following Mikael's advice to document the issues with the proposal hoping for feedback.

Given that the deadline is today, I've submitted this proposal.

Andrew C. Morrow

unread,
Apr 11, 2013, 12:02:08 PM4/11/13
to std-pr...@isocpp.org

Hi all -

I will not be attending the Bristol meeting to advocate for this proposal, which was accepted as N3619 (http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3619.html).

If anyone who is interested in the material and plans to attend would be willing to advocate for it, I would appreciate it. My understanding is that papers without advocates usually go unconsidered.

If you are interested, please email me directly.

Thank you,
Andrew

Reply all
Reply to author
Forward
0 new messages