-1

I have a basic message class that I am using to serialize data and then send that data through a socket:

std::vector<char> m_rawMessageData;
int m_currentReadPosition = 0;

template<typename writeDataType>
void LoadData(writeDataType &inData)
{
    int dataSize = sizeof(writeDataType);
    int currentSize = m_rawMessageData.size();

    m_rawMessageData.resize(dataSize + m_rawMessageData.size());
    std::memcpy(&m_rawMessageData.at(currentSize), &(inData), dataSize);
}

template<typename ReadDataType>
int GetData(ReadDataType &outData)
{
    std::stringstream dataReader;

    int dataSize = sizeof(ReadDataType);

    if (m_currentReadPosition + dataSize > m_rawMessageData.size())
        return 0;

    std::memcpy(&outData, &m_rawMessageData.at(m_currentReadPosition),
        dataSize);  

    m_currentReadPosition += dataSize;

    return dataSize;
}   

I know there are some possible bugs/errors in this code if these are called incorrectly, but I'm the only developer right now, and I need to get something working. Also, I'm assuming that only basic types will be passed, and that endiness is the same on both ends.

I want this message class to be able to handle structs. My thought was this: in each of my structs I will write a "Serialize/Deserialize" function. Then, in my LoadData and GetData calls, I will check if there is a "Serialize/Deserialize" function and then call it instead of doing the generic memcpy.

I was able to check if there is a serialize function using something similar to the code in this question: Check if a class has a member function of a given signature

But I'm not sure how I am suppose to call the "Serialize" function. I can't simply use "inData.Serialize()", as that fails to compile.

So in short, I want my LoadData Function to look something like this:

template<typename writeDataType>
void LoadData(writeDataType &inData)
{
    //Check if writeDataType has Serialize Function. 
    if (serializeDoesExist)
    {
         inData.Serialize(m_rawMessageData);
    }
    else
    {
        int dataSize = sizeof(writeDataType);
        int currentSize = m_rawMessageData.size();

        m_rawMessageData.resize(dataSize + m_rawMessageData.size());
        std::memcpy(&m_rawMessageData.at(currentSize), &(inData), dataSize);
    }
}

Any help would be much appreciated.

NOTE: My boss does not like to add external libraries to project unless absolutely necessary. That's why I haven't gone with Protobuf or something similar.

3 Answers3

1

With std::experimental::isdetected, you can create the traits:

template<class T>
using serialize_t = decltype(std::declval<T>().Serialize());

template <typename T>
using has_serialize = std::experimental::is_detected<serialize_t, T>;

and then, using SFINAE:

template<typename writeDataType>
std::enable_if_t<has_serialize<writeDataType>::value>
LoadData(writeDataType &inData)
{
    inData.Serialize(m_rawMessageData);
}

template<typename writeDataType>
std::enable_if_t<!has_serialize<writeDataType>::value>
LoadData(writeDataType &inData)
{
    int dataSize = sizeof(writeDataType);
    int currentSize = m_rawMessageData.size();

    m_rawMessageData.resize(dataSize + m_rawMessageData.size());
    std::memcpy(&m_rawMessageData.at(currentSize), &(inData), dataSize);
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Very nice, it even simplifies the detection step. Any chance this could be done with only C++14? If not, I'll go ahead and mark this as the answer, since it's both elegant and straightforward. – Tyler Jackson Nov 29 '17 at 20:32
  • `std::experimental::detected` can be implemented with C++11. (link give possible implementation, dependencies as `std::void_t` can also be done in C++11). – Jarod42 Nov 29 '17 at 20:38
0

You can use a try-catch statement to check if this function exists.

try{
    inData.Serialize();
}catch(<You can specify the exception here, or just write "..." to catch any exception>...){
    <What you want to do if the function does not exists>
}

If you want more detailed information about try-catch and exception handling: https://www.tutorialspoint.com/cplusplus/cpp_exceptions_handling.htm

Lucky
  • 319
  • 1
  • 6
  • I don't believe this will work, since "inData" is a typename. The problem is I can't write inData.Serialize() in the first place, as that causes a compiler error. – Tyler Jackson Nov 29 '17 at 19:30
  • Sorry, my mistake, I saw "inData" as an instance. Then you probably have to use SFINAE. Check out this question: https://stackoverflow.com/questions/257288/is-it-possible-to-write-a-template-to-check-for-a-functions-existence – Lucky Nov 29 '17 at 19:38
0

I would suggest that you write a LoadData/GetData pair that are specialized using std::enable_if, checking for the presence of the needed member. The specialized forms use those members while the generic forms continue to use memcpy

SoronelHaetir
  • 14,104
  • 1
  • 12
  • 23
  • Do you mean write a specialized LoadData/GetData for every struct I have defined in my program? A possible solution I suppose, but I would really like to keep it generic. – Tyler Jackson Nov 29 '17 at 19:32
  • No, I meant use std::enable_if and your member detection to choose a specialization that would use the member if it is present but have the not-present pair continue to use memcpy. It might turn out that both the present and not-present forms of LoadData and GetData would need an std::enable_if parameter (one where the condition is true the other where it is not). That way each LoadData/GetData has only the code appropriate to that code path. – SoronelHaetir Nov 29 '17 at 22:19