Pages: 1 ... 3 4 [5]   Go Down
Author Topic: A function to handle multiple datatypes  (Read 5855 times)
0 Members and 1 Guest are viewing this topic.
North Queensland, Australia
Offline Offline
Edison Member
*
Karma: 70
Posts: 2172
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

No, a pointer would be disastrous.  p would be an address to a pointer.

You can overload the function, but still isn't sufficient for arrays.
Code:
/*May cause ambiguities occur, if so the function prototypes need more specific matches. ( all constant data can be accessed via references, so constant pointers may not be able to match a higher overload.*/
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

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

Or partially specialise a class template:
Code:
template <typename T> struct Sender{

  static void Send( const T& value ){

    const byte* p = (const byte*) &value;
    for (unsigned int i = 0; i < sizeof( value ); i++)
      Serial.write (*p++);
  }
}

template <typename T> struct Sender< T* >{

  static void Send( const T* value ){

    const byte* p = (const byte*) value;
    for (unsigned int i = 0; i < sizeof( T ); i++)
      Serial.write (*p++);
  }
}

You can mix overloads with templates, so you can have a non-template function for char* and templates for everything else.
As with the class method you could do a full specialisation ( different from the partial specialisation above ) for the character arrays.
In the char* specific version, code could check for a null char.
« Last Edit: November 04, 2012, 04:34:11 am by pYro_65 » Logged


Finland
Offline Offline
Sr. Member
****
Karma: 1
Posts: 270
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

After what pYro_65 just demonstrated, I am inclined to claim that for OP's case the sendAnything(const byte *value, unsigned int size) is more reasonable that all the extra hassle that the template alternative would require.

Code:
int MyInt = 42;
char *MyString = "foo";
sendAnything((const byte *)&MyInt, sizeof(MyInt));
sendAnything((const byte *)MyString, strlen(MyString));
Logged

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

After what pYro_65 just demonstrated, I am inclined to claim that for OP's case the sendAnything(const byte *value, unsigned int size) is more reasonable that all the extra hassle that the template alternative would require.

Well let's not get too carried away. In five pages of this thread I am the only one who has posted an actual working sketch, and that sketch showed that the templated solution was simple, and produced shorter code. The rest is hypothetical talk about needing to serialize "an object" or "lots of different types".

Sure, you can invent an example where pointers might be simpler or produce less code, but how about seeing an actual example of the types of data that need to be serialized? In other words, the real requirement, not some sort of hypothetical requirement.

It would have been helpful if the original post had actually quoted some code, or the actual data types, rather than just "a string of characters or an integer or an object". That's pretty vague. Does the string of characters contain every possible value (eg. including 0x00)? Is it a 2-byte integer? What sort of object is it?

Is the receiving end an Arduino? Does it have the same endian-ness? Does it have the same float representation? Is Unicode involved? How do you handle errors? Do you want sumchecks? How do you know if you are receiving the string of characters, or the integer, or the object?
Logged


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

"all the extra hassle that the template alternative would require."

Agreed. All this talk of "this approach has some shortcomings so it should not be used" isn't understanding what coding is all about: making compromises so you produce a flawed and optimal solution to the task at hand.
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

Quote
It would have been helpful if the original post had actually quoted some code, or the actual data types, rather than just "a string of characters or an integer or an object". That's pretty vague. Does the string of characters contain every possible value (eg. including 0x00)? Is it a 2-byte integer? What sort of object is it?
Is the receiving end an Arduino? Does it have the same endian-ness? Does it have the same float representation? Is Unicode involved? How do you handle errors? Do you want sumchecks? How do you know if you are receiving the string of characters, or the integer, or the object?

OK, as requested, more information.
The receiving end is not an arduino, but a machine running Ubuntu. I am fairly certain that is has the same endian-ness as everything I have sent so far I have managed to receive without having to switch the byte-order. Floats are tricky, due to different ways of representing them and their inherent inaccuracy, so I am not going to use any. Nope, no unicode. Error checking will be via a checksum byte at the end of the serial packet (note: I have not implemented this bit yet). Immediately prior to calling sendAnything the program outputs 0xFE, which signifies the start of the frame, followed by a one byte code signifying the type of data being sent and another byte containing the address or destination code. After all the data is sent the code 0xFE is sent again to signify the end of the frame.

With this in mind the sendAnything function can be re-written to look like this:
Code:
template <class T> void sendAnything(const T& value, byte type, byte address)
{
    const byte* p = (const byte*)&value;
    unsigned int i;
    Serial.write(0x7E);
    Serial.write(type);
    Serial.write(address);
    for (i = 0; i < sizeof(value); i++) {
      if (*p == 0x7E || *p == 0x7D) {
        Serial.write(0x7D);
        Serial.write(*p++ ^ 0x20);
      } else {
        Serial.write(*p++);
      }
    }
    Serial.write(0x7E);
}

The data types I will have to send are: 8, 16 and 32 bit integers, both signed and unsigned. Strings of up to 20 characters in length, null terminated as per normal strings.
At the moment I don't need to send objects or structures, but I may have to in the future.

So with that in mind, maybe sendAnything should be sendSomeThings  smiley-razz

« Last Edit: November 04, 2012, 07:23:34 am by Chris Parish » Logged

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

In that case, use pointer to an unsigned char + sizeof.
Logged

Seattle, WA USA
Online Online
Brattain Member
*****
Karma: 616
Posts: 49445
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
He probably expects to send the strings without terminating zeros.
foo1 and foo2 have terminating NULLs.

Code:
char nonsense[4] = { 'c', 'r', 'a', 'p' };
does not.

Though why one would want to do this is unclear. The definition of a string is "a NULL terminated array of chars", so your premise is flawed from the start.
Logged

UK
Offline Offline
Shannon Member
****
Karma: 223
Posts: 12630
-
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Though why one would want to do this is unclear.

That depends which representation of the string we're talking about. It's reasonable to expect strings to be null-terminated in memory, but I wouldn't normally expect the terminator to be persisted or serialised.
Logged

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

Seattle, WA USA
Online Online
Brattain Member
*****
Karma: 616
Posts: 49445
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
but I wouldn't normally expect the terminator to be persisted or serialised.
Not serialized is reasonable. But, somehow, the size of the array (the number of non-NULL characters in the array) needs to be communicated.

I'm not sure what you mean by not persisted. If you mean, for instance, that when writing the string to a file, the NULL would not be written, I'd agree. But, still, the size of the string needs to be communicated somehow. Typically, that is done by replacing the NULL with a CR/LF, which allows some process that reads the data to know where the stream of characters ends.
Logged

Finland
Offline Offline
Sr. Member
****
Karma: 1
Posts: 270
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
He probably expects to send the strings without terminating zeros.
foo1 and foo2 have terminating NULLs.

Code:
char nonsense[4] = { 'c', 'r', 'a', 'p' };
does not.

Yes, I am very well aware that foo1 and foo2 have terminating nulls and that char nonsense[4] in your example doesn't. Instead of writing "He probably expects to send the strings without terminating zeros.",  I perhaps should have written "When he sends strings, he probably doesn't want to send the terminating zeros."

Though why one would want to do this is unclear. The definition of a string is "a NULL terminated array of chars", so your premise is flawed from the start.

My premise wasn't flawed. My premise was that when he sends null terminating char arrays a.k.a strings, he probably wants to send the non-null characters only and not the terminating nulls. I am not sure  if thats what the OP wanted, but it certainly is a valid assumption. And that is something that was not possible with Nick's templated sendAnytihing().
« Last Edit: November 04, 2012, 10:38:21 am by pekkaa » Logged

UK
Offline Offline
Shannon Member
****
Karma: 223
Posts: 12630
-
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Yes, the string length needs to be known or communicated somehow. My point was that this is not usually done by null-terminating the string in its serialised or persisted form; null termination is how the C runtime library happens to indicate the string length, but this method is not widely used once you step outside the C runtime. For example, when you write a string to a text file the text is not null-terminated; instead, the file system explicitly records the length of the file in most file systems. When you write a string to a network stream, the network protocol provides a way of indicating the length of the string or denoting the end of the string, but these are typically not null-terminated. By the same logic, there's no reason to expect a serialisation mechanism that transfers strings between computers to also transfer the null terminator; the null is only relevant within the context of a computer that is storing the string within a null-terminated array and is not part of the actual string value.
Logged

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

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

Time to count the horse's teeth.

Here's the code:

Code:
#if (ARDUINO >= 100)
#include <Arduino.h>
#else
#include <WProgram.h>
#endif

#define PRINT_THAT_THING(X) print_anything((void *)&(X),sizeof(X)) // an evil macro!

struct S { int i; char j; char* p;} s; // a struct for testing
 
void setup()
{
  Serial.begin(115200);
  int n;
  n = PRINT_THAT_THING("1234567890");
  int a[] = {1,2,3,4,5,6,7,8,9,10};
  n = PRINT_THAT_THING(a);
  n = PRINT_THAT_THING(s);
  s.i = n; s.j = 'c'; s.p = NULL;
  n = PRINT_THAT_THING(s);
  n = PRINT_THAT_THING(Serial);
  n = PRINT_THAT_THING(n);
}

void loop()
{
}

int print_anything(void *v,int n) // get a raw address in the form of a void pointer
{
  uint8_t *p = (uint8_t *)v; // recast as a pointer to byte so we can use it for our evil purposes
  for (int i=0; i < n; i++) {
    uint8_t b = *p++; // get next byte value (BwaaHaHaHAha...)
    Serial.print("0x");
    Serial.print(b,HEX); // send it out the serial port
    Serial.print(" ");
  }
  Serial.print("\nThat thing had ");
  Serial.print(n);
  Serial.print(" bytes in it!\n\n");
  return n; // return no of bytes sent
}

which produces this:

Code:
0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x30 0x0
That thing had 11 bytes in it!

0x1 0x0 0x2 0x0 0x3 0x0 0x4 0x0 0x5 0x0 0x6 0x0 0x7 0x0 0x8 0x0 0x9 0x0 0xA 0x0
That thing had 20 bytes in it!

0x0 0x0 0x0 0x0 0x0
That thing had 5 bytes in it!

0x5 0x0 0x63 0x0 0x0
That thing had 5 bytes in it!

0x49 0x1 0x5D 0x1 0xC5 0x0 0xC4 0x0 0xC0 0x0 0xC1 0x0 0xC6 0x0 0x4 0x3 0x7 0x5 0x1
That thing had 19 bytes in it!

0x13 0x0
That thing had 2 bytes in it!

And I would just like to say that if I have inadvertantly offended anyone's religion by posting this, I *really* don't care at all. 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.)

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

@pico, your code only is aliased by a char type, which is what the standard says is acceptable for aliasing, therefore it has not subverted type safety, nor done any evil casts.

@all who are interested,
There are some important differences with types that I may not have made clear in my post earlier on ( here ).
When attempting to interpret data differently ( other than char ); the thing you have to remember is char is the only type you can cast to safely ( unless casting operators for the appropriate types are implemented into user defined types. Does not apply to primitive/built-in types like 'int' ). In my earlier post I had this example below.

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

The difference being here is i_StreamPtr is aliasing void* with a non compatible type ( int ).
Also a few commonly accepted usages ( that are wrong ):

Code:
//Not just void* but any incompatible types.
uint32_t u_Value32 = 0x00ABCD00;
uint16_t u_Value16 = *( uint16_t* ) u_Value32;

//And the incompatible types reversed.
uint16_t u_Array[] = { 0xBAD, 0xF00D, 0x00 };
uint32_t u_Int32 = * ( uint32_t* )  u_Array;

These are all examples of the strict aliasing rule being broken. Which is why casts are evil, in particular void* as it always masks the original type of the incoming data. This is fundamentally why C++ constructs are far better for this job, especially templates; as with the template definition, you are always provided  with a 'known' type.
« Last Edit: November 04, 2012, 09:09:50 pm by pYro_65 » Logged


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