Re: Use of swap in the standard library
Am 27.11.2010 01:21, schrieb Nikolay Ivchenkov:
I see several potential issues with certain parts of the standard
library specification related to swap functions.
1)
According to 17.6.1.1/3,
"Whenever a name x defined in the standard library is mentioned, the
name x is assumed to be fully qualified
as ::std::x, unless explicitly described otherwise. For example, if
the Effects section for library function F
is described as calling library function G, the function ::std::G is
meant".
Also note that according to 17.6.4.4 [global.functions]/4:
"Unless otherwise specified, global and non-member functions in the standard
library shall not use functions from another namespace which are found through
argument-dependent name lookup (3.4.2). [ Note: The
phrase ?unless otherwise specified? is intended to allow argument-dependent
lookup in cases like that of ostream_iterators: Effects:
*out_stream << value;
if (delim != 0)
*out_stream << delim;
return (*this);
?end note ]
17.6.1.1 is about library contents in general, and 17.6.4.4 specifically
concentrates on some non-member functions that have granted special allowance to
do so. Generally, a more specific requirement "overrides" a more general
requirement.
For exactly this reason it makes normative difference, if the library would omit
the std:: in front of move and forward calls, which are non-member functions. It
is therefore no *superfluous* wording, when [allocator.requirements]/2 says:
"Within Tables 41 and 42, the use of move and forward
always refers to std::move and std::forward, respectively."
In all other places of the library specification move and forward must
specifically called as std::move and std::forward, if these functions are
intended to be called instead of possible overloads provided by the user.
Now let's look at iter_swap specification:
template<
class ForwardIterator1,
class ForwardIterator2>
void iter_swap(
ForwardIterator1 a,
ForwardIterator2 b);
Effects: swap(*a, *b).
Requires: a and b shall be dereferenceable. *a shall be
swappable with (20.2.2) *b.
If in the call swap(*a, *b) name 'swap' is supposed to be unqualified
(considering the intention of the "Requires" paragraph), where it is
explicitly stated?
This is a consequence of the definition of the Swappable requirements
which define the context in which a binary non-member swap function shall be
called combined with the allowance specified in above mentioned
[global.functions]/4.
Moreover, I don't see where the description of
std::iter_swap explicitly requires to provide declarations of
std::swap from<utility> header before the invocation swap(*a, *b).
2)
20.2.2/1 contains:
"This subclause provides definitions for swappable types and
expressions."
I don't see any definition for swappable types, though there is
definition of ValueSwappable types.
This is an introductory sentence. It doesn't allow for far-reaching
interpretations. The swappable requirements of 20.2.2 can be participated into
several groups of different generality. They more or less correspond to the
previous concepts HasSwap<T, U> and Swappable<T> plus a new ValueSwappable
requirement set. The definitions refer to types which is indirectly a
consequence of the introductory definition of types T and U and later referring
to them. The specialization defined in p. 4
"An rvalue or lvalue t is swappable if and only if t is swappable with any
rvalue or lvalue, respectively, of type T."
is also referring to a type T. Since the term swappable is overloaded here, it
does not really help to say that a type T is swappable, because that does not
answer everything necessary. The ValueSwappable requirements were introduced
because the collection of requirements of this refining requirement set was
rather often occurring in the library.
The wording was specifically written to support proxy types which may provide
the following overload set:
struct A { /../ };
struct Proxy { /../ };
void swap(A&, A&);
void swap(Proxy, A&);
void swap(A&, Proxy);
20.2.2/2 contains:
"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 [...]"
AFAICS, t and u should be expressions referring to objects, not just
objects.
Please quote the complete part, this meaning is implied. Bullet 2 says:
? 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.
20.2.2/4 contains:
"An rvalue or lvalue t is swappable if and only if t is swappable
with any rvalue or lvalue, respectively, of type T"
Some lvalues and rvalues do not refer to objects. For example, there
are no expressions that can be swappable with lvalue *(int*)0.
Such expressions can occur in code that does does not potentially evaluate
operands, as in decltype expressions.
A glvalue of cv-unqualified type T can refer to an object of type 'const
T' (cv-qualifier can be discarded by const_cast). The wording should
be more precise with respect to considered kinds of lvalues and
rvalues.
Currently I see now reason how this could lead to problems. The definition of
/swappable/ does not invalidate core language rules or other rules that would
lead to undefined behaviour.
Note also that there exist rare but legal use-cases of swapping a const object,
especially if that is an proxy object. Similar rare but sometimes useful as
evaluating
std::is_assignable<const T&, const T&>::value
for proxies that occur in unusual but known assignment situations.
3)
I don't understand why class member functions with name 'swap' are not
considered for the purposes of defining swappable expressions.
It was considered as too late: Concepts had been removed. They would have
allowed to take advantage of a concept maps to "equalize" both member and
non-member functions. If you would have added this requirement that would mean
that the library would have needed to provide a template tool that would do all
the stuff necessary to lead to the same form of expression for usage of member
swap or non-member swap and everyone would be required to use this tool to
provide the proper context of a swap operation based on swappable requirements.
Requirement tables are based on valid expressions and it is not reasonable
requirement that such a requirement is:
"either the expression a.swap(b) or the expression swap(a, b) is valid".
Generic code must *know* what kind of expression has to be used, so above
fictive requirement would be practically use-less.
If our
class provides appropriate member function, why should we define
additional non-member function with the same semantics? I would prefer
to use some exchange function that works like this:
-----------------------------------------------------
#include<type_traits>
#include<utility>
template<class T>
struct has_member_swap_impl
{
template<class U, class B, class R>
static typename std::enable_if
<!std::is_volatile<U>::value>::type
test(R (B::*)(U&));
template<class U, class B, class R>
static typename std::enable_if
<!std::is_volatile<U>::value>::type
test(R (B::*)(U&)&);
template<class U, class B, class R>
static typename std::enable_if
<std::is_volatile<U>::value>::type
test(R (B::*)(U&) volatile);
template<class U, class B, class R>
static typename std::enable_if
<std::is_volatile<U>::value>::type
test(R (B::*)(U&) volatile&);
template<class U>
struct Identity {};
struct HasSwap
{
template
<
class U,
class = decltype(test<U>(&U::swap))
HasSwap(Identity<U>) {}
};
static bool const value =
std::is_constructible
<
HasSwap,
Identity<T>
::value;
};
template<class T>
struct has_member_swap :
std::integral_constant
<
bool,
has_member_swap_impl<T>::value
{};
template<class T>
void exchange_impl(T&t1, T&t2, std::true_type)
{
t1.swap(t2);
}
template<class T>
void exchange_impl(T&t1, T&t2, std::false_type)
{
using std::swap;
swap(t1, t2);
}
#define STATIC_ASSERT(expr) static_assert(expr, #expr)
template<class T>
void exchange(T&t1, T&t2)
{
STATIC_ASSERT(!std::is_const<T>::value);
(exchange_impl)(t1, t2, has_member_swap<T>());
}
Sure, that is the library tool I was referring to. But it was late in the game
after concept removal. No-one wanted to risk the invention of a tool that has
such a fundamental impact and little time to be tested in all situations. E.g.
the constraint on non-const arguments is intentionally not imposed on the
current swappable requirements.
-----------------------------------------------------
If I didn't miss something, ::exchange(t, u) attempts to call
t.swap(u) iff:
- t and u are lvalues of the same type cv T (where cv is volatile or
empty cv-qualification), and
- T is a class type which has public non-static non-template member
function "swap", whose name in the call t.swap(u) can be unambiguously
found (see 10.2), and this function has single explicit parameter of
type cv T& and implicit object parameter of type cv B& (where B is T
or its base class type), but does not have any other parameters or
ellipsis, and
- T has no any member function template with name "swap" that can be
found during name lookup in t.swap(u) (this is consequence of possible
core language defect).
Otherwise, if the first condition is satisfied, ::exchange(t, u)
follows currently approved ADL-based approach.
It is a nice idea, but it was much too late to invent something like this after
concept removal. I would say, that above suggestion is at least controversial in
some aspects as mentioned above. What if someone had intended to have a private
swap function but a public non-member swap function? The tentative decision to
make SFINAE contexts access-aware is extremely new and did not exist, when the
Swappable requirements where introduced in the library. This would lead to
different behaviour. I don't want to say that this might not be a good idea, but
it requires more than just an one thought to make such a decision. Last but not
least the general Swappable concepts are intentionally written to support
mixed-type swaps, which is not possible with the exchange template as currently
defined (see A and Proxy above).
HTH & Greetings from Bremen,
Daniel Kr?gler
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]