Pages: 1 2 [3] 4 5   Go Down
Author Topic: A function to handle multiple datatypes  (Read 5259 times)
0 Members and 1 Guest are viewing this topic.
Germany
Online Online
Faraday Member
**
Karma: 56
Posts: 2974
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Just one other quick question:
How might I modify the above code to send the bytes MSB first?

You can modify any part of your for ( ; ; ) { }  construct. ?!
Or do you want to "turn around" every single byte bit by bit  ? ( MSB = Most Significant Bit  ? )  

Edit: luckily there are no smileys containing space characters
« Last Edit: November 02, 2012, 06:13:28 am by michael_x » Logged

Poole
Offline Offline
Jr. Member
**
Karma: 0
Posts: 89
I'm not a complete idiot. Some bits are missing
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Or do you want to "turn around" every single byte bit by bit

Ignore the whole thing about MSB first. It isn't relevant. My mistake.

Quote
Honestly, this is why you shouldn't fiddle with pointers if you don't understand what you are doing.

Whilst I will happily admit that my knowledge of pointers is far from encyclopaedic the code to which you refer is a simple bit wise XOR operation.
The whole point of the bit stuffing is to allow me to use a customised data packet for transmission over the serial port. The packet uses the value 0x7E to signify the start and the finish of the packet. However, this means that if the packet payload data contains the value 0x7E then we have to modify it so that the receiver does not think it had reached the end of the packet, thereby causing data loss. So what we do is to replace 0x7E with 0x7D and then follow it with the original value XOR'd with 0x20 giving 0x5E. We also follow any other occurrence of 0x7D with 0x7D XOR'd with 0x20.
Upon receiving the packet any occurrence of 0x7D is discarded and the following byte XOR'd with 0x20 to obtain the original value, thereby "unstuffing" the bytes.
Honestly, this is why you shouldn't fiddle with bitwise operations if you don't understand what you're doing.  smiley-wink
Logged

North Queensland, Australia
Offline Offline
Edison Member
*
Karma: 64
Posts: 2100
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

The point is, you don't want to defeat type checking.
Yes. Sometimes you do.
Why do you think compiler-writers built that in?
Equally, why do you think they built in void pointer?

Type checking is very useful, a great invention. Sometimes however, it is nice to be able to side-step it if it gets in your way. C allows that.

Nick's advice is sound. It is a serious flaw expecting C++ behaviour to reflect that of C. You do not want to break type checking by using a void*, there are uses for these types but destroying type safety is not one of them.

The strict aliasing rule must be adhered to, you cannot assume your compiler ( or even subsequent versions of it ) will always provide working 'undefined behaviour'.

Even in the function by Chris Parish,

Code:
const byte* p = (const byte*)(const void*)&value;

In this line, the void* cast explicitly breaks the strict aliasing rule, whereas simply casting to char* ( or byte* )  wouldn't.
Logged


Offline Offline
God Member
*****
Karma: 32
Posts: 828
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

You do not want to break type checking by using a void*, there are uses for these types but destroying type safety is not one of them.

Well sorry, but actually, I do want to break type checking in some cases. And I do. And that is precisely what void* is for, both in C and C++ -- to work with a pointer without a type.

As for "type safety", as we have already discussed, whether or not the gun is "safe" or not depends on whether you can shoot straight.

C is a sharp language. Assembly even sharper. When dealing with sharp objects, it pays to know what you are doing, and be to be careful. Otherwise, accidents can and will happen.

But if sharp objects make you nervous, you can always choose to use the safety scissors instead. Choices are good.
Logged

WiFi shields/Yun too expensive? Embeddedcoolness.com is now selling the RFXduino nRF24L01+ <-> TCP/IP Linux gateway: Simpler, more affordable, and even more powerful wireless Internet connectivity for *all* your Arduino projects! (nRF24L01+ shield and dev board kits available too.)

New Hampshire
Offline Offline
God Member
*****
Karma: 17
Posts: 781
There are 10 kinds of people, those who know binary, and those who don't.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Well whist you guys were fighting I tried this and it seems to work. Thanks

Code:
template <class T> void sendAnything(const T& value)
{
    const byte* p = (const byte*)(const void*)&value;
    unsigned int i;
    for (i = 0; i < sizeof(value); i++) {
      if (*p == 0x7E || *p == 0x7D) { //byte stuffing
        Serial.write(0x7D);
        Serial.write(*p++ ^ 0x20);
      } else {
        Serial.write(*p++);
      }
    }
}

Casting a template argument in a generic function to a void pointer is a bad practice.  You've created a function that allegedly sends anything.  It will work with the intrinsic types and it will work with some class types, but it will definitely not send anything (although it will always send something, whether it makes sense or not).

The question that begs asking is, what data type are you attempting to transmit that requires casting to a void pointer (and then to a byte pointer)?

Your template function should be relying on either an overloaded Serial.write() method for sending the passed in data, or the user passing in the data in a form that is compatible with the Serial.write() method.  You're hiding a void cast inside a generic function.  Just to reiterate, bad practice.  

Your function would basically allow all sorts of nonsense code to compile.  ie:

sendAnything(Serial);
sendAnything(someComplexList);
etc.

A more appropriate function name would be sendAnythingViaVoid_EvenIfItDoesntMakeSense().

Quote
Well sorry, but actually, I do want to break type checking in some cases. And I do. And that is precisely what void* is for, both in C and C++ -- to work with a pointer without a type.

The cases to break type safety are when there are no better options available.  This is NOT one of those cases.

Quote
But if sharp objects make you nervous, you can always choose to use the safety scissors instead. Choices are good.
Choices are indeed good.  Veiled petty insults... another story.

Logged


Offline Offline
God Member
*****
Karma: 32
Posts: 828
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


The cases to break type safety are when there are no better options available.  This is NOT one of those cases.

I'm sorry, but the approach I suggested to the OP (which was essentially the same as that already provided by Coding Badly), had absolutely nothing to do with templates. It was also perfectly "safe", general, and even elegant, if implemented correctly. It also happens takes up less space in flash, as I think I may have mentioned in passing.

If "type safety" is paramount before all other considerations in writing YOUR code, well, good luck to you. Just don't get preachy with me with the bizarro fundamentalist religion stuff about "type safety". What do you guys do when there are no types to protect you? Oh that's right, "assembly" is the language who must not be named. Dear me, dear me... the wringing of the hands.

Quote
Choices are indeed good.  Veiled petty insults... another story.

Veiled? How clumsy of me.  smiley-mr-green
Logged

WiFi shields/Yun too expensive? Embeddedcoolness.com is now selling the RFXduino nRF24L01+ <-> TCP/IP Linux gateway: Simpler, more affordable, and even more powerful wireless Internet connectivity for *all* your Arduino projects! (nRF24L01+ shield and dev board kits available too.)

New Hampshire
Offline Offline
God Member
*****
Karma: 17
Posts: 781
There are 10 kinds of people, those who know binary, and those who don't.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


The cases to break type safety are when there are no better options available.  This is NOT one of those cases.

I'm sorry, but the approach I suggested to the OP (which was essentially the same as that already provided by Coding Badly), had absolutely nothing to do with templates. It was also perfectly "safe", general, and even elegant, if implemented correctly. It also happens takes up less space in flash, as I think I may have mentioned in passing.

I was not commenting on any code beyond what I quoted in my post.  I had no criticisms towards the code posted by Coding Badly because it was quite clear that it relied on void pointers.  My actual criticism was made quite clearly in my previous post.  Not just once, but twice, so I'm not going to bother reiterating it for a third time.

Second, any usage of void pointer is NOT perfectly "safe".  To paraphrase Indigo Montoya, "I do not think that word means what you think it means.".  There are acceptable uses of void pointer, even appropriate uses.  I never argued that there weren't, though you seem to be under the impression that I have.

My original question still stands to the OP, and to everyone in general even, what types of data are being transmitted that requires the use of void pointer.  Without that information, it seems rather shortsighted to me to claim any solution to the problem is the best solution.

If "type safety" is paramount before all other considerations in writing YOUR code, well, good luck to you.

Where did I state type safety was "paramount before all other considerations"?  I don't believe I did.  Actually, I'm absolutely positive that I did not.

You may want to work on your reading comprehension.  It appears to be a bit lacking.
Logged


Offline Offline
God Member
*****
Karma: 32
Posts: 828
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I was not commenting on any code beyond what I quoted in my post.  I had no criticisms towards the code posted by Coding Badly because it was quite clear that it relied on void pointers.

Well, good. In that case we are complete agreement.

Second, any usage of void pointer is NOT perfectly "safe".  To paraphrase Indigo Montoya, "I do not think that word means what you think it means.".

I think I know what "safe" means. It has a much broader meaning than "type safe". If you have your own take on it, more power.

There are acceptable uses of void pointer, even appropriate uses.  I never argued that there weren't, though you seem to be under the impression that I have.

True. Since you seemed to be part of a howling chorus gnashing their teeth and renting their garments because a (gasp) void pointer solution was being proposed instead of "type safe" overloading, templates etc., this concession has surprised me and encouraged me. You're not a complete dingbat zealot card-carrying member of the type-safe Taliban. My apologies for mistaking you for one.

My original question still stands to the OP, and to everyone in general even, what types of data are being transmitted that requires the use of void pointer.  Without that information, it seems rather shortsighted to me to claim any solution to the problem is the best solution.

The OP's question was "A function to handle multiple datatypes". The OP went on further to explain that he simply wants to be able to send a string of bytes out the serial port that belong to a variable/array/structure of any type. In this case, a void pointer approach was one of three obvious approaches. I read the thread quickly, and not realising that Coding Badly had already covered this approach, restated it.  

Then there came some discussion about how this approach was inherently unsound, unsafe, a gun that will shoot you in the foot, etc. I, however, believe that the void pointer approach in this case, on balance, is actually far superior to both function overloading and templates.

Why? The first reason I've already stated twice. The overloading and template approach waste flash memory needlessly. The second reason is that these approaches are inherently both more complex and maintenance intensive. And therefore more error prone, in fact, rather than less.  The void pointer method proposed independently by Coding Badly and myself actually cuts to the chase very simply and directly. If the programmer understands what void pointers are and how to use them, there is little room for error here. Testing is also simplified, since you are really dealing with a single function.

Where did I state type safety was "paramount before all other considerations"?  I don't believe I did.  Actually, I'm absolutely positive that I did not.

Well, there is this:

The cases to break type safety are when there are no better options available.  This is NOT one of those cases.

Perhaps I misconstrued. But I did qualify my interpretation of your stance with "if". smiley-wink

And perhaps we were thinking about different code. I certainly wasn't referring to anything beyond my (and Coding Badly's) original proposed approach. If you are really OK with that, as you have stated above, then there isn't much ground for disagreement.

Quote
You may want to work on your reading comprehension.  It appears to be a bit lacking.

Ooh. Is that one of those "veiled petty insults"? LOL. Given the heat of the moment, I'll let it slide!  smiley-mr-green

« Last Edit: November 02, 2012, 11:15:55 am by pico » Logged

WiFi shields/Yun too expensive? Embeddedcoolness.com is now selling the RFXduino nRF24L01+ <-> TCP/IP Linux gateway: Simpler, more affordable, and even more powerful wireless Internet connectivity for *all* your Arduino projects! (nRF24L01+ shield and dev board kits available too.)

Offline Offline
Edison Member
*
Karma: 116
Posts: 2205
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
If there any way that I can write a function that will accept multiple datatypes?

Easy: use a pointer to void.
Logged

UK
Offline Offline
Shannon Member
****
Karma: 222
Posts: 12520
-
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I want a function that I can pass either a string of characters or an integer or an object and it will treat it as a stream of bytes so that I can write them out to the serial port one byte at a time. The function will also include things like byte stuffing, but I can deal with that later.

In that situation the most obvious solution to me is to define overloaded functions which accept the different types of object you want to  output. So some overloads might accept C strings, others might accept serialisable objects, various others might accept primitive types. Each overload would implement the logic to determine the range of bytes to be output for that argument type and call some common function to output those bytes. This approach gives minimal code duplication and above all is simple.
Logged

I only provide help via the forum - please do not contact me for private consultancy.

Poole
Offline Offline
Jr. Member
**
Karma: 0
Posts: 89
I'm not a complete idiot. Some bits are missing
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Easy: use a pointer to void.

Guessing you didn't read the whole thread above. Turns out that people seem to have VERY strong opinions as to the use of void pointers to allow you to cast between incompatible types.

I have been doing some reading of my own and I feel that the void pointer is the best way for me to achieve what I want as I want to totally disregard the type of data and just see it as a stream of bytes.

Thanks for your help, folks!
Logged

Offline Offline
Edison Member
*
Karma: 116
Posts: 2205
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
Turns out that people seem to have VERY strong opinions as to the use of void pointers to allow you to cast between incompatible types.

Not surprising.

Quote
I have been doing some reading of my own and I feel that the void pointer is the best way for me to achieve what I want as I want to totally disregard the type of data and just see it as a stream of bytes.

Good decision.
Logged

Germany
Online Online
Faraday Member
**
Karma: 56
Posts: 2974
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

the void pointer is the best way for me to achieve what I want as I want to totally disregard the type of data and just see it as a stream of bytes.
If the caller of your sendAnything() always provides an address and the size to send, and if the receiver by miracle knows what to expect, you're completely right. 
Else you should also read about Serialization, which deals with everything else you need, beyond a void*.

void sendAnything(void * p, size_t len); is easy to implement, agreed.
It just leaves the problems to the caller, there's always some place where you cannot "totally disregard the type of data".
You need at least a bunch of overloads, if you want to support something like
Code:
int i;
char* text ="Hello";
char array[10] ={ -1, 0, 1, 2};
prog_uchar message[] PROGMEM  = {"IF YOU FORGET ME, YOU WILL MISS ME"};
MyClass myObject(1,2);  // best any supported class provides its serialization methods.

sendAnything((i);
sendAnything(text);
sendAnything(array);
sendAnything(F("Version 0.1"));
sendAnything(message);
sendAnything(myObject);
Logged

North Queensland, Australia
Offline Offline
Edison Member
*
Karma: 64
Posts: 2100
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Easy: use a pointer to void.

Guessing you didn't read the whole thread above. Turns out that people seem to have VERY strong opinions as to the use of void pointers to allow you to cast between incompatible types.

I agree there are strong opinions, but for good cause.

Aggression and arguments shouldn't be necessary; this isn't opinion; it is fact. And to follow analogies, its similar to arguing: Its legal to drive across the traffic lights while they are red, simply because you can see no cars.

If you are not considering why we posted our concerns, how about asking for a proof why it is safe. C/C++ is built upon a set of rules; if it is allowed, the standard will state it.

Quote
I have been doing some reading of my own and I feel that the void pointer is the best way for me to achieve what I want as I want to totally disregard the type of data and just see it as a stream of bytes.
Good decision.

Not a good decision, this is also wrong, as stated before. Arranged into a different scenario, still, the code below is not valid. There are no if's, or but's, it is just how C++ rolls.

Code:
void PrintIntFromStream( void *v_Stream ){
  int *i_StreamPtr = ( int* ) v_Stream;
  Serial.print( "Int aliased from stream: " );
  Serial.println( *i_StreamPtr  );
  return;
}

If you read my post above, I mentioned casting directly to char* is fine, because it is allowed and would fix your code. However the void* breaks the strict aliasing rule and therefore subsequent casts are undefined behaviour.

A large part of the problem is the strict aliasing rule is an addition to the standard, therefore material based on the standard before the addition are rampant throughout the net. Also it is interpreted differently between C and C++, so trying to port broken ideas in C only make things worse in C++.

Take a moment to read this:
Quote
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
— a type compatible with the effective type of the object,
— a qualified version of a type compatible with the effective type of the object,
— a type that is the signed or unsigned type corresponding to the effective type of the object,
— a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a sub aggregate or contained union), or
— a character type.

This is an actual excerpt from the standard, if you managed to grasp all that, you may have noticed the last line. This is the rule on which I based my fix for your function. The one basic aliasing tool is char, for reasons such as endianess and alignment ( there is method in their madness ).

There is nothing in that list that explicitly mentions a cast to incompatible types including void* types. The reason your code works ( if it does ) like my example above, is it relies on undefined behaviour. What that actually means is, the code you have written has not met any conditions for optimisations that rely on the strict aliasing rule. When one can be applied the compiler is free to disregard interactions with aliased data. I say 'disregard' with a notion that there are some circumstances where you reverse the optimising capacity of the compiler.

You may say, "'optimisations', this isn't part of the discussion", but guess what, the strict aliasing rule is pretty much specifically for them. This is why you don't get aliasing compile errors, as some of the optimisations are linker based, after the compiler has done its thing. This rule actually matters!!!

For clarity:
Quote
Strict aliasing is an assumption, made by the C (or C++) compiler, that dereferencing pointers to objects of different types will never refer to the same memory location (i.e. alias each other.)

Now for the fun part.

Using Arduino IDE 1.5

 - AVR Compiler version 4.3.2
 - Optimisation flags 'Os'

This flag is the compile for size option ( i.e ) removing dead code. It also includes all 'O2' optimisations which include... <drum roll>
-fstrict-aliasing compiler specification which enforces the notion that you obey the rule. The only way to deal with aliased incompatible types safely is through non-portable attributes... Or use C++ properly.
Logged


Global Moderator
Offline Offline
Brattain Member
*****
Karma: 474
Posts: 18696
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Interesting explanation, pYro_65.

This compiles, BTW, and does not use a void pointer:

Code:
template <typename T> void sendAnything (const T& value)
  {
  const byte* p = (const byte*) &value;
  for (unsigned int i = 0; i < sizeof value; i++)
      Serial.write (*p++);
  }  // end of sendAnything

I got rid of the "packet stuffing" part but you can put that back if you want. In fact the EEPROMAnything file, described here also works if you remove the void cast.
Logged

Pages: 1 2 [3] 4 5   Go Up
Jump to: