17

I'm trying to understand the conditions on std::swap from [C++11: utility.swap]. The template is defined as

template <typename T> void swap(T &, T &)

(plus some noexcept details) and as having the effect of "exchanging the values stored at the two locations".

Is the following program have well-defined?

#include <utility>

int main()
{
    int m, n;
    std::swap(m, n);
}

If I wrote swap code myself (i.e. int tmp = m; m = n; n = tmp;), it would have undefined behaviour, since it would attempt lvalue-to-rvalue conversion on an uninitialized object. But the standard std::swap function does not seem to come with any conditions imposed on it, nor can one derive from the specification that there is any lvalue-to-rvalue and thus UB.

Does the standard require std::swap to perform some magic that is well-defined on uninitialized objects?

To clarify the point, consider the function void f(int & n) { n = 25; }, which never has undefined behaviour (since it does not read from n).

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 3
    Your program leaves `m` and `n` undefined before even calling `swap`. It's not `swap`'s job to define them; both parameters to `swap` are expected to be defined. So I'm not seeing where the problem is. – Mike DeSimone Jun 20 '14 at 21:31
  • Well at least for C++1y I would say that for sure [using and indeterminate int would be undefined](http://stackoverflow.com/questions/23415661/has-c-standard-changed-with-respect-to-the-use-of-indeterminate-values-and-und). I don't think anything `swap` could do would change that. I would probably say the intention for C++11 was probably the same. The only exception would be in the case of unsigned narrow chars which would retain their indeterminate values. – Shafik Yaghmour Jun 20 '14 at 21:31
  • @MikeDeSimone: well, it's just kind of impossible to implement, as you can see from my example attempt... And where does it say that "both parameters are expected to be defined"? – Kerrek SB Jun 20 '14 at 21:33
  • @ShafikYaghmour: So does `std::swap` mandate library magic, or is it missing a condition? – Kerrek SB Jun 20 '14 at 21:34
  • 1
    @ShafikYaghmour: I don't follow... please see my update. The problem does not manifest at the call site; `f(a)` is perfectly valid. – Kerrek SB Jun 20 '14 at 21:36
  • @Kerrek: Again, that argument could be made about almost any function that takes an input, not just `swap`. Your `f(n)` function completely disregards any prior value of `n`, and is an exception (and representative of the class of functions that use passed-in pointers or references for *output-only* parameters, as opposed to *input-output* parameters). – Mike DeSimone Jun 20 '14 at 21:39
  • @MikeDeSimone: I understand all that. Can you please quote the standard parts that codify this? – Kerrek SB Jun 20 '14 at 21:41
  • Looking at the VS13 implementation, they seem to use a custom `move()` function – yizzlez Jun 20 '14 at 21:41
  • Would `swap` have to use "library" magic? What if it was implemented as something like `char *temp=malloc(sizeof(p1)); memcpy(temp, p1, sizeof(p1)); memcpy(p1, p2, sizeof(p1)); memcpy(p2, temp, sizeof(p1)); free(temp)`? I think `memcpy` is defined even on indeterminate values, is it not? – supercat Apr 17 '15 at 20:43

2 Answers2

12

Very nice question. However, I would say this is covered by [res.on.arguments]§1:

Each of the following applies to all arguments to functions defined in the C++ standard library, unless explicitly stated otherwise.

  • If an argument to a function has an invalid value (such as a value outside the domain of the function or a pointer invalid for its intended use), the behavior is undefined.

To address your concern about f(n), the function f from your question is not a part of the C++ standard library and thus the above clause does not apply to it.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • It's ill-specified, but I agree. – Joseph Mansfield Jun 20 '14 at 21:40
  • How is `swap(m, n)` different from `f(m)`? The function arguments are *valid* in both cases. – Kerrek SB Jun 20 '14 at 21:40
  • @KerrekSB It's different in that `f` is not a part of the C++ standard library and thus this requirement does not apply to it. – Angew is no longer proud of SO Jun 20 '14 at 21:41
  • @Angew: I think that's missing the point. You're arguing in terms of "invalid values"; I'm saying that there are no invalid values here. – Kerrek SB Jun 20 '14 at 21:42
  • 1
    Any function in the C++ standard library that works like your `f(n)` example *will define an exception* stating that the incoming value is irrelevant and either left unchanged or overwritten. – Mike DeSimone Jun 20 '14 at 21:43
  • @KerrekSB If an "invalid value" can be "a pointer invalid for its intended use," then an uninitialised value can be invalid for being exchanged with another one. That clause is not the clearest formulation, but I believe it applies. – Angew is no longer proud of SO Jun 20 '14 at 21:45
  • 2
    The problem is that "invalid value" is not well defined. I suspect it is supposed to cover cases such as this though. – Joseph Mansfield Jun 20 '14 at 21:45
  • @Angew: That's completely unrelated. There are no pointers involved here. The reference is bound to a perfectly valid (albeit uninitialized) lvalue. – Kerrek SB Jun 20 '14 at 21:46
  • @MikeDeSimone: Exception to *what*? What's that rule that we need an exception from? I think that's what I'm looking for. When you define `int a;`, `a` is a perfectly valid value. It's just not initialized, so you cannot "read" it (i.e. convert it to an rvalue). – Kerrek SB Jun 20 '14 at 21:46
  • 1
    @KerrekSB I know there are no pointes involved in `swap`, but "such as" is not an exclusive formulation. I meant: if a valid pointer can be an "invalid value" because it's "invalid for its intended use," so can an uninitialised value be an "invalid value." – Angew is no longer proud of SO Jun 20 '14 at 21:48
  • @Kerrek: An exception to [res.on.arguments] above. You did see the "unless explicitly stated otherwise" in it, right? Maybe that trigger gets pulled somewhere? Anyway, since you intend to restrict this discussion to language lawyers who own a copy of the standard (which I don't) while not operating under any similar restriction yourself, I'm out of here. – Mike DeSimone Jun 20 '14 at 21:50
  • @MikeDeSimone: Yes, this is very much about languagese... sorry :-S – Kerrek SB Jun 20 '14 at 21:51
  • Passing an uninitialized variable to `foo1(int)` would invoke Undefined Behavior, but passing an uninitialized variable to `foo2(int &)` would be not only legitimate but entirely reasonable if the purpose of `foo2` is to initialize the passed-in variable. If `swap` is passed references to two legitimate storage locations of matching type, it could be implemented to swap their contents in a fashion that was guaranteed not to invoke Undefined Behavior regardless of whether those variables were initialized or not. Is there any reason `swap` shouldn't be required to be implemented in such a way? – supercat May 21 '15 at 21:40
1

As the value of M is undefined, I would expect it to taint the call to swap. Nasal Demons may fly, when swap is called.

EvilTeach
  • 28,120
  • 21
  • 85
  • 141
  • Unfortunately, the standard text ([utility.swap]) does not seem to mention any restrictions or conditions for `T = int`. – Kerrek SB Jun 20 '14 at 21:41
  • There is nothing unfortunate about it. The expectation that there is specific documentation telling you what undefined behavior will occur for all functions for all compiler options is unrealistic. – EvilTeach Jun 21 '14 at 14:44
  • @EvilTeach: It would be possible and useful to specify that if `x` is defined and `y` is undefined prior to `swap(x,y)`, then following the swap `x` will be undefined and `y` will hold the former value of `x`. I'm not sure in what way it would be more useful to declare that `swap(x,y)` must be unreachable whenever `x` (or `y`) is undefined, even if in every situation where `swap(x,y)` would be reachable without `x` being defined, the value of `y` will be abandoned without having been used. – supercat Apr 17 '15 at 20:48
  • Otherwise, I would say that if `swap` were specified as using one or both of its operands as rvalues, then invocation without specifying two valid rvalues would be UB, but in the absence of such specification I see no reason to allow an implementation to access either operand as an rvalue *unless* it could guarantee that doing so would not cause Undefined Behavior. – supercat Apr 17 '15 at 20:58