0

I am trying to learn some C++ coming from C#. One of the things I liked in C# was the extensions like CustomItem.ToString() and was curious how I could implement something like that in C++. I am using std::vector<unsigned char> to store a buffer and then processing it byte by byte.

I have the following function:

int DataParser::ToLittleEndianInt(std::vector<unsigned char> ba) //Little Endian
{
    long int Int = 0;
    int arraySize = ba.size();
    if (arraySize ==4)
    {
        Int = ba[0] | ((int)ba[1] << 8) | ((int)ba[2] << 16) | ((int)ba[3] << 24);
    }
    else if (arraySize == 2)
    {
        Int = ba[0] | ((int)ba[1] << 8);
    }
    else if (arraySize == 1)
    {
        Int = ba[0];
    }
    return Int;
}

Here I can send it a vector from one to 4 bytes and it will convert to integer for me. Is there a way for me to use it in a way like this:

std::vector<unsigned char> CurrentBytes(4);
for (int i = 0; i < 4; i++)
    CurrentBytes[i]=1;

// how can we do this?
int results = CurrentBytes.ToLittleEndianInt();
//or
int results = CurrentBytes->ToLittleEndianInt();

I just feel it is quite readable and want to have extensions for strings, dates, int, dollars and so on.

UPDATE:

I tried doing the custom class as suggested, but I am getting compile time errors. I put this in my .h file:

class Byte
{
    public:
        Byte();
        ~Byte();
        std::string  DataType;
        int  ColumnWidth;
        std::vector<unsigned char> data;
        int ToLEInt() {
            long int Int = 0;
            int arraySize = this->ColumnWidth;
            if (arraySize == 2)
            {
                Int = this->data[0] | ((int)this->data[1] << 8);
            }
            else if (arraySize == 1)
            {
                Int = this->data[0];
            }
            return Int;
        };
};

That throws the error:

Error   LNK2019 unresolved external symbol "public: __thiscall Byte::Byte(void)" (??0Byte@@QAE@XZ) referenced in function "public: static void __cdecl DataParser::Parse(class nlohmann::basic_json<class std::map,class std::vector,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,bool,__int64,unsigned __int64,double,class std::allocator,struct nlohmann::adl_serializer>)" (?ParseToDataTable@Parse@@SAXV?$basic_json@Vmap@std@@Vvector@2@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@_N_J_KNVallocator@2@Uadl_serializer@nlohmann@@@nlohmann@@@Z)    

I also tried declaring it as:

const Byte& ToLEInt(const Byte& s)const {}

That was the way I saw done in Stephen Prata's C++ Primer, but then I still get errors, and I would have to use it like this in my cpp:

std::vector<unsigned char> test(3);
for (int i = 0; i < 2; i++)
        test[i] = i;
    Byte CurrentBytes;
    CurrentBytes.data = test;
    CurrentBytes.ColumnWidth=2;
    int results = CurrentBytes.ToLEInt(CurrentBytes);
    //instead of
    int results = CurrentBytes.ToLEInt();

I then tried putting the declaration outside of the class block, but then I got an error that LEInt wasn't defined,

int Byte::ToLEInt(){}

When I tried adding int ToLEInt(); to the class block, then it complained that it was already defined.

Community
  • 1
  • 1
Alan
  • 2,046
  • 2
  • 20
  • 43
  • What happens if you decide to use a `std::deque` instead of vector? Or `std::list`? Or any sequence container, even a plain old array? Copy the same code `x` times, with the only change being the container type? C++ has the concept of iterators and sequences. For example, look how `std::sort` works. – PaulMcKenzie Oct 16 '19 at 20:27
  • [Look at boost endian](https://www.boost.org/doc/libs/1_71_0/libs/endian/doc/html/endian.html). The point is that you should strive to not lock yourself into using a specific container. – PaulMcKenzie Oct 16 '19 at 20:32
  • Instead of trying to add a custom extension function, in C++ just create a free-standing function or free-standing template function. If you want to see a janky workaround for custom extension functions (but don't do it in C++!) for curiosity's sake, q.v. https://stackoverflow.com/questions/57080495/is-it-possible-add-member-function-to-data-type-in-c – Eljay Oct 16 '19 at 20:41
  • @PaulMcKenzie I am looking at deque, it looks like for my particular use case it may end up with a performance hit since it uses 2 pointers for each reference, but I will give it a try once things are working. I did try a basic array, I don't remember at the moment why I had to switch to a vector, but maybe I can explore it again. – Alan Oct 17 '19 at 15:02
  • @Alan -- Well my point is that you shouldn't care what container is ultimately chosen. The `ToLittleEndianInt` code should just work without *any* changes and without code duplication. Other languages do not have this ability, while C++ does. It's called "generic programming". Again look at `std::sort` or even `std::reverse` -- you don't see a separate function for vector, deque, etc. – PaulMcKenzie Oct 17 '19 at 15:11

2 Answers2

0

The unresolved external symbol "public: __thiscall Byte::Byte(void)... error means that the linker program couldn't find a definition (a body) of the constructor of your class Byte. In order to fix this you can add empty braces to your constructor and destructor declarations (this time without semicolons) like below:

class Byte
{
    public:
        Byte() {}
        ~Byte() {}
        ...

or just remove their declarations at all since currently they're idle anyway:

class Byte
{
    public:
        //Byte();  - can be removed entirely
        //~Byte(); - can be removed entirely
        ...

When you don't provide a constructor the compiler implicitly generates the default constructor so you neither have to worry about its declaration nor definition (body). However when you declare one you need to provide its definition yourself (at least empty braces).

bloody
  • 1,131
  • 11
  • 17
  • 2
    *This is why inheritance was invented.* -- Not for `std::vector`. Inheriting from `std::vector` should be avoided. – PaulMcKenzie Oct 16 '19 at 20:25
  • [Thou shalt not inherit from std::vector](https://stackoverflow.com/questions/4353203/thou-shalt-not-inherit-from-stdvector) – Yksisarvinen Oct 16 '19 at 20:31
  • @bloody I updated my question with a custom class, still having some trouble I can't explain. – Alan Oct 17 '19 at 15:05
0
// how can we do this?
int results = CurrentBytes.ToLittleEndianInt();
//or
int results = CurrentBytes->ToLittleEndianInt();

You can't. C++ doesn't work that way.

What you can do is this instead:

// assuming DataParser is a namespace
using namespace DataParser;
int results = ToLittleEndianInt(CurrentBytes);
// or...
int results = DataParser::ToLittleEndianInt(CurrentBytes); 

That's the C++ way to handle it.

If you want a more elegant solution, you could inherit from std::vector, though this is not recommended.

A much better approach would be to create your own class:

class myContainer {
    private:
        std::vector data;
    public:
        //...
        int ToLittleEndianInt() {
            // Process data...
            return result;
        }
};

Then your code would become:

myContainer CurrentBytez;
//...
int results = CurrentBytes.ToLittleEndianInt();

Of course, creating your own class will take some research, particularly on how classes differ in C++ from C#, but it might be worth it. You might want to learn it anyway, so it's not really a big deal.