tags:

views:

176

answers:

2

when copying arrays using std::copy is there a way to check to see if the copy went through correctly? especially if you have like hex values in an unsigned char array. I am not sure how one can verify that the copy went through and even if it did the values that were in one array copied over correctly.

+3  A: 

Virtually the only way for a copy to fail is if you're copying to something like a back_insert_iterator, in which case there's a possibility of allocation in the destination container failing. This will normally1 be reported by an exception being thrown.

1For those who want the gory details, in this case "normally" means you haven't provided your own allocator that does something other than throwing an exception in case of failure.

Edit: Perhaps I should have been more explicit: I was talking about a failure that you'd stand a chance of detecting. The precondition on std::copy says that the destination iterator must not be in the source range. Violating that leads to undefined behavior. Likewise, if you do an assignment (either directly or via an algorithm like copy or transform) that's out of range of the target type, you (again) get undefined behavior.

The definition of undefined behavior is that the standard places no requirements on the implementation. In other words, if you do this, anything can happen. Nothing about the behavior of the program is guaranteed at all. There's no sensible way to talk about how to detect such a thing having happened, because the language says that if your program does such a thing, you can't depend on anything.

Edit again: Since there seems to be some misunderstanding about undefined behavior. To do any good, if you're concerned with things that might lead to undefined behavior, you need to prevent them from happening, rather than attempting to do the copy and then try to figure out whether it succeeded. By then it's too late; if the program does something that's undefined, all the behavior of the program is undefined.

So, to deal with the possibility of the destination iterator being within the range of the source iterators, or that the source data is outside the range of the destination data type, you need to check for those up-front, and never even attempt the undefined behavior.

template <class T, class U>
safe_copy(T source_start, T source_end, U dest) { 
    if (std::less(source_start, dest) && std::less(dest, source_end))
        throw(std::range_error("Output iterator within input range");
    std::transform(source_start, source_end, dest, checked_convert);
}

For the moment, I haven't tried to define checked_convert. It might do a saturating conversion, or throw an exception if a value is out of range, etc., -- depending entirely on the type of data involved. One way or another, however, it assures that the source value fits in the destination range before assignment to the destination is attempted.

Jerry Coffin
There are at least two other ways: the destination range beginning inside the source range, and overflow (eg copying a range of `long` to a range of `int`).
Potatoswatter
@Potatoswatter: Thanks for bringing that up. I've added a bit to the answer that should (hopefully) clarify the situation with respect to situations like those.
Jerry Coffin
Still a couple mistakes: the destination *initial* iterator can't be in the target range; the final iterator may be. Different types do not produce undefined behavior; `copy` is defined to assign objects from the source to the destination, which is defined iff the source and destination types define that operation. (If it compiles, it will probably be well-defined!)
Potatoswatter
To put it another way: `int8_t(128)` may be undefined but `uint8_t(128) == `int8_t(128)` has a 100% chance of being false. From the sound of his question, that's exactly what he's trying to deal with.
Potatoswatter
@Potatoswatter: An assignment from (for example) long to int is *required* to compile, but if the value is out of range of the target type, will give undefined behavior.
Jerry Coffin
IF the value is out of range, it will give behavior that is undefined and undesirable. Moreover, it is also detectable, and we should provide him with a means of detection. He wants to eliminate overflow errors. Is this really that hard?
Potatoswatter
@Potatoswatter:For about the fourth time: it is only detectable *before* the fact! If you want to do some good, you need to *prevent* the undefined behavior from ever happening.
Jerry Coffin
@Jerry: if you insist that it's only detectable before the fact, then give some advice on before-the-fact detection, which is not hard. I agree my code isn't bulletproof, but it's pretty much good enough for catching bugs and won't fail on signed-to-unsigned underflow. You're just waving your hands and yelling OP for doing the wrong thing.
Potatoswatter
And can you give me a single example where `wide(N) == narrow(wide(N))` and overflow occurs? For a two numbers of two types to be equal, they must be the same number, representable in both types!
Potatoswatter
@Potatoswatter:You persist in trying to treat undefined behavior as if it gave defined results. All I can advise is that you read and reread §1.3.12 of the C++ standard until you realize that this just isn't how things work.
Jerry Coffin
@Jerry: that doesn't enter into it. First, casting integral types never produces undefined behavior per §1.3.12: it won't crash. §1.3.5 is more relevant. See §4.7. Conversion to an unsigned destination type is *always* defined. Conversion to signed produces an implementation-defined value, ONLY if the value cannot be represented, which is NOT undefined behavior. This implementation-defined value must nevertheless be representable in the destination type. Therefore if the source value is representable, the destination will be equal to it.
Potatoswatter
If it is unrepresentable, it will not be equal to ANY possible value of the destination type, so the comparison must fail. QED
Potatoswatter
Also, now that I've actually looked it up, just to drive it home: if he's converting `signed` to `unsigned` `char`'s (or `int`'s or whatever), the result of that conversion is always exactly defined, nothing implementation-specific about it. `uint8_t(-1) == 255` unambiguously.
Potatoswatter
@Potatoswatter: sorry, but once again no. There are some conversions that are currently implicitly undefined. In C++ 0x the undefined behavior will be made explicit (e.g. see CWG issue #707) http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#707.
Jerry Coffin
As for the rest of your comments, yes, it's true that some conversions always succeed -- but for those, by definition the copy also succeeds, so they're irrelevant.
Jerry Coffin
@Jerry: do you even understand that page you linked? Prove you do with an example. And maintain a straight face.
Potatoswatter
Also, OP doesn't care whether the copy succeeds (he assumes it did as his question presupposes that he is executing the next line of code), he cares whether "the values that were in one array copied over correctly."
Potatoswatter
@Potatoswatter:Yes, I understand the page to which I linked. The question is not what's proven, but whether you've understood what's proven.
Jerry Coffin
You can't provide an example of the behavior made undefined by that issue because it doesn't happen with IEEE floats and integers less than 64 bits. Your understanding is very questionable. Of course, `float(2)` is allowed to crash a machine where `numeric_limits<float>::max() == 1.f`, which is entirely possible, but sounds funny. You are still waving your hands and need to show how my code could incur undefined behavior, or a false-positive equality due to implementation-defined values. It should take very little effort to give an example.
Potatoswatter
@Potatoswatter:you're just digging yourself an even deeper hole. First of all, nothing requires IEEE FP. Second, FLT_MAX has always been a minimum of 1e+37, so no a value of 2 can't trigger this particular problem. The fact that you think any of this is about *your* code causing undefined behavior means you don't understand the issue at all yet. The problem is that if the copy itself causes undefined behavior, then no other code has defined behavior either.
Jerry Coffin
Jerry, `numeric_limits<float>::max()` may equal 2, not FLT_MAX, *because* IEEE compliance is not required. Float and double can mean pretty much anything if `numeric_limits<float>::is_iec559 == false`. The OP specifically indicated that he is casting to an unsigned type, which means there is no undefined behavior and no undefined values in his `copy` or my `equal`. To claim that the machine might randomly crash because at some point in the past a `copy` converted `INFINITY` to `int` indicates a misunderstanding of how computers work. Please, give an EXAMPLE of causing undefined behavior!
Potatoswatter
* note, there may be undefined behavior if he casts `float` s to `unsigned char` s, but in practice only a few platforms might throw an FP exception inside `copy` and in all probability INFINITY (or 128) will convert to some integral value, which will not convert back to INFINITY when promoted for comparison.
Potatoswatter
@Potatoswatter:Where you do you come up with these insane ideas? FLT_MAX goes all the way back to C89/90, predating all mention of IEC 659 floating point. FLT_MAX and numeric_limits<float>::max() are exactly the same properties of the same type -- there is no possibility that they can differ at all. If you look he specifically says "**like** hex values in an unsigned char array". Clearly that's an example, *not* the only possible target type. As far as not crashing, I'll direct you again (since you clearly don't understand it yet) to the definition of undefined behavior.
Jerry Coffin
IEC559 is IEEE754 which is from 1985. You need to start quoting standards. As far as not crashing, I'll direct you again to section 4.7, which covers all integral conversions and guarantees that an integral conversion never crashes! That leaves floating-point types, which for all remotely modern systems are governed by IEEE754, NOT the C/C++ standards. Do you deny that?
Potatoswatter
The assertion that you think undefined behavior occurs is not a silver bullet to win arguments. You need to specify *when* it occurs. You provided one example above — when an integral type overflows a floating-point type. But you haven't provided an example of how that would happen, because it requires either a native integer longer than 128 bits or a float shorter than 32 bits (and probably without infinity). Your argument really has nothing to do with error-checking on a `copy`; you are just claiming that because casts such as that are undefined, no casts should ever be performed.
Potatoswatter
Fortunately, it is possible to specify what one is doing, in order to avoid undefined behavior. I claim that my code will work for integral types, and I posit that it will work on a machine using IEEE FP under any conditions that allow the execution to reach the `std::equal` call in the first place. Disprove WITH AN EXAMPLE.
Potatoswatter
I've never claimed that no cast should be performed -- I'm claiming that if you're concerned about the possibility of the cast being inadequate, checking beforehand accomplishes more than trying to do so afterward. The point isn't that the call to std::equal will fail, but exactly that it might well not execute to start with. As for an example of std::equal failing, consider a UDT with `bool operator==(unsigned char) { throw 1; }`. If you're going to pose challenge, at least try to make it a bit challenging.
Jerry Coffin
@Jerry: only native types are relevant to this discussion. (You *should* be able to come up with an example involving only native types.) See the edit to my answer.
Potatoswatter
A: 

You can double-check that the copy succeeded by using this: (assert comes from <cassert>)

assert ( std::equal( src_first, src_last, dst ) );

This will catch problems resulting from src_first < dst < src_last. A better way, though, would be to assert that condition directly: (this requires random-access iterators, ie from a vector or deque, and they must belong to the same container — which is usually the case if you're concerned with overlap)

assert ( src_last < dst || dst <= src_first );

But you seem concerned with other kinds of problem, such as type compatibility. There is no silver bullet for that, but the first suggestion above will keep you safe as long as the source and destination types can be tested for equality.

An aside: hex is just the base-16 number system, so any data is "hex values" if written in base-16.

Edit: I got a couple downvotes because assert isn't appropriate for production code, and because typecasts are supposedly unsafe because some cases are not defined by the C++ standard.

First, if you don't want to assert, then remove the assert and use the condition for something else! Not rocket science.

Second, it's your responsibility to ensure that you don't crash with a numeric exception. First off, there are no exceptions and no undefined behavior in integer casts. So the above use of std::equal is guaranteed to work for any native integral types and values, period. Also not rocket science.

If you are using floating-point types, then C++ allows the possibility of a crash and burn when if you cast a float to an int, using std::copy or otherwise: it leaves the behavior of a float-to-int overflow undefined. Does this mean you can't ever cast a float to an int without first checking that overflow won't occur? Of course not! C++ does not define the behavior, but IEEE 754 does. Moreover, IEEE 754 defines all the behavior of all the operations you can perform (undefined values notwithstanding), and it has been ubiquitous for well over 20 years.

IEEE 754 further recommends that the default operating environment never raise any numeric exceptions, and POSIX guarantees this. That means that you can do anything to any floating-point numbers and not worry about crashing! (As long as you are in a POSIX or similar numeric system and you haven't specifically enabled exceptions.)

Crashing and "undefined behavior" out of the way, testing for equality afterwards is numerically a solid operation. C++ compares native-type numbers by casting (promoting) the operand of "lesser" type to one of "greater" type, per §5 ¶7 of the standard. Equality will only hold if both values can be represented in the type of greater range, and they are the same number in that type.

Values which are not numbers may fail. If you copy a vector with a NaN in it, the std::equal call will return false because NaN cannot equal anything else, not even an identical NaN, because NaN stands for Not a Number and equality isn't something it does.

Hence my disclaimer "not a silver bullet… safe as long as the source and destination types can be tested for equality." You need to generally be aware of what you're doing. Pushing the bounds of floating-point tends to cause headaches. Do your homework.

But crashing will not happen, undefined behavior will not incur that, despite the standard's disclaimer about undefined behavior, because the behavior IS defined somewhere else, either in IEEE 754 or (unlikely) in your FPU manual if it is not compliant.

Edit 2: the checked_convert function mentioned in the other answer is just dancing around a well known utility, boost::numeric_cast. If you want to safely cast numbers and get specific information about how the operation failed, if it did, that is the tool you want. Don't attempt to write your own code, it's tricky.

Potatoswatter
These are, frankly, useless suggestions. Neither will dependably do anything useful. In the first case, you're questioning the ability of the hardware to copy data from one place to another, but assuming that the same hardware *will* do the comparison correctly. There's no more reason to believe the comparison will be right than the copy. In the second case, you're comparing pointers that don't necessarily (in fact normally won't) refer to parts of the same array -- which gives undefined results. If you honestly want to do the second part use std::less, and do it *before* the copy.
Jerry Coffin
@Jerry: did you read anything I wrote on this page? There are two kinds of problems which are easily missed: overlapping the ranges improperly, and overflow as a result of differing types. It has nothing to do with hardware failures, and I'm sorry you've never encountered these errors… you will someday. As for undefined results, that is true, I'll add a disclaimer… in practice it works for `vector::iterator` and `deque::iterator` but will throw if extra checking is enabled.
Potatoswatter
Yes, I read it. No, I don't think that advising somebody to start by causing undefined behavior, *then* attempting to see whether it's caused a problem is good advice.
Jerry Coffin
@Jerry: good code can produce bounded values. There are valid reasons to want to down-cast. This "advice" isn't appropriate here.
Potatoswatter