1

Guys I have a function like this (this is given and should not be modified).

void readData(int &ID, void*&data, bool &mybool) {
    if(mybool)
    {
       std::string a = "bla";
       std::string* ptrToString = &a;
       data = ptrToString;
    }
    else
    {
        int b = 9;
       int* ptrToint = &b;
       data = ptrToint;
    }
}

So I want to use this function in a loop and save the returned function parameters in a vector (for each iteration). To do so, I wrote the following struct:

template<typename T>
struct dataStruct {
     int id;
     T** data;   //I first has void** data, but would not be better to 
                 //  have the type? instead of converting myData back 
                 // to void* ?
     bool mybool;
};

my main.cpp then look like this:

int main()
{
    void* myData = nullptr;
    std::vector<dataStruct> vec;  // this line also doesn't compile. it need the typename
    bool bb = false;

    for(int id = 1 ; id < 5; id++) {
        if (id%2) { bb = true; }
        readData(id, myData, bb);       //after this line myData point to a string
        vec.push_back(id, &myData<?>); //how can I set the template param to be the type  myData point to?       
    }
}

Or is there a better way to do that without template? I used c++11 (I can't use c++14)

Gaetan
  • 577
  • 2
  • 7
  • 20
  • 4
    `Guys I have a function like this (this is given and should not be modified).` Given by whom? There is a lot of undefined behavior in that function. – tkausl Jun 19 '18 at 14:04
  • 12
    Whoever gave you that function needs to stop coding. You are getting pointers to local variables which means when the function end they are dangling pointers. – NathanOliver Jun 19 '18 at 14:05
  • 2
    It is returning address of a local variable. Give it back to whoever gave it to you. – William J Bagshaw Jun 19 '18 at 14:06
  • Can you have a reference to a `void` pointer? – Joseph D. Jun 19 '18 at 14:06
  • given that `mybool` is passed in - why would anyone do this? Just have 2 read data's and then switch on the bool, case 1 go read a string which can just be returned; case 0 go read an int that can be returned - no need for this void* rubbish. – UKMonkey Jun 19 '18 at 14:24
  • 1
    @codekaizer: Can? Yes. Should? No. – MSalters Jun 19 '18 at 15:19
  • Don't focus on readData(...), it is just for the sake of the example and is well coded in my real case. The point is, in main(...), void* myData is passed by reference to readData and once readData has returned, myData is pointing to a type safe (int, string, ...). The goal is to store myData in each iteration in a vector with the type it point-to – Gaetan Jun 20 '18 at 09:32

4 Answers4

0

The function that you say cannot be modified, i.e. readData() is the one that should alert you!

It causes Undefined Behavior, since the pointers are set to local variables, which means that when the function terminates, then these pointers will be dangling pointers.

gsamaras
  • 71,951
  • 46
  • 188
  • 305
  • Technically, by itself the function doesn't cause UB. You just get UB after it returns, if you ever attempt to use any of the...stuff it stores into `data`. But no, it doesn't do anything even vaguely useful--any attempt at using what it produces gives UB. – Jerry Coffin Jun 19 '18 at 14:11
  • @codekaizer A reference to a thing is the same as the thing. It just means the thing from call site will register any changes. – NathanOliver Jun 19 '18 at 14:12
  • @NathanOliver, so that goes to say that dangling reference and dangling pointers are the same too? – Joseph D. Jun 19 '18 at 14:13
  • 1
    @codekaizer Broken is broken. Use either and you have UB. – Goswin von Brederlow Jun 19 '18 at 14:20
  • @GoswinvonBrederlow, I want to understand not just the behavior but also the grammar, the types, etc. Would be glad if you could answer my question. – Joseph D. Jun 19 '18 at 14:47
  • Implementation wise a reference is just the address in memory just like a pointer. Semantically a reference has the promise that it isn't nullptr. Grammatically a pointer requires dereferencing `a->foo` while a reference does that implicitly and is used just like the object itself `a.foo`. But both reference and pointer being just an address in memory and if that address becomes invalid, e.g. because it points to a stack frame of a function and you exit that function, they both break. – Goswin von Brederlow Jun 19 '18 at 14:54
  • @gsamaras, True, but the readData(...) here was just for the sake of the example and is well coded in my real case. The point is, in main(...), void* myData is passed by reference to readData and once readData has returned, myData is pointing to a type safe (int, string, ...). The goal is to store myData in each iteration in a vector with the type it point-to – Gaetan Jun 20 '18 at 09:57
0

Let us leave aside the shenanigans of the readData function for now under the assumption that it was just for the sake of the example (and does not produce UB in your real use case).

You cannot directly store values with different (static) types in a std::vector. Notably, dataStruct<int> and dataStruct<std::string> are completely unrelated types, you cannot store them in the same vector as-is.

Your problem boils down to "I have data that is given to me in a type-unsafe manner and want to eventually get type-safe access to it". The solution to this is to create a data structure that your type-unsafe data is parsed into. For example, it seems that you inteded for your example data to have structure in the sense that there are pairs of int and std::string (note that your id%2 is not doing that because the else is missing and the bool is never set to false again, but I guess you wanted it to alternate).

So let's turn that bunch of void* into structured data:

std::pair<int, std::string> readPair(int pairIndex)
{
  void* ptr;
  std::pair<int, std::string> ret;
  // Copying data here.
  readData(2 * pairIndex + 1, ptr, false);
  ret.first = *reinterpret_cast<int*>(ptr);
  readData(2 * pairIndex + 2, ptr, true);
  ret.second = *reinterpret_cast<std::string*>(ptr);
}

void main()
{
  std::vector<std::pair<int, std::string>> parsedData;
  parsedData.push_back(readPair(0));
  parsedData.push_back(readPair(1));
}

Demo

(I removed the references from the readData() signature for brevity - you get the same effect by storing the temporary expressions in variables.)

Generally speaking: Whatever relation between id and the expected data type is should just be turned into the data structure - otherwise you can only reason about the type of your data entries when you know both the current ID and this relation, which is exactly something you should encapsulate in a data structure.

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • "Let us leave aside ..." That's exactly the case. readData(..) in my real case is well coded. The example here was to simulate the fact that in main(..), void* myData is passed by reference to a function (readData) and once this function return, myData then point-to a type-safe (int,...). The goal is to save myData with the type it point-to into a vector. Your solution is very specific to the example I gave. I was expecting a more generic approach. Let assume void* myData is the only param of readData(void*&data) {..}. Is there a way to save myData in a vector with the type it point to? – Gaetan Jun 20 '18 at 09:29
  • Only if you know the type it points to. All you get is apparently a `void*` pointing somewhere into memory, and you will only find bytes there. You must know what those bytes mean (i.e. what type they have) to read/save them, because that information is not stored in the `void*`. And the point still stands, you cannot really store different kinds of data in the same vector (you could store the `void*`s but that would do nothing to help you figure out the type). Tell us *how* you would know the type of data `readData` wrote and we can help you. – Max Langhof Jun 20 '18 at 12:50
  • thanks for your reply. So in the main(..) function I gave above, after this line readData(id, myData, bb); I still don't know the type myData is pointing-to? I was thinking since myData is passed by reference to readData, when readData return myData has been converted from void* myData to int* mydata (for example) and I can do something like vec.push_back({id, &myData}); where getType is (an imaginary function) supposed to return me int as type.. – Gaetan Jun 20 '18 at 14:53
  • Indeed, `readData` cannot change the type of `myData`. This kind of notion doesn't even exist in C++ - you cannot have your variables' types changed by passing them to functions (although you could emulate that to an extent with polymorphy or, say, `std::variant`). The type of the data written to a `void*` is not stored anywhere, it is solely your responsibility to keep track of it (and never attempt to read it as the wrong type). That is precisely why `void*` are to be avoided (no pun intended) in C++. It would be best to ask a new question with more detail on your actual problem. – Max Langhof Jun 20 '18 at 15:15
0

Your readData isn't a useful function. Any attempt at using what it produces gives undefined behavior.

Yes, it's possible to do roughly what you're asking for without a template. To do it meaningfully, you have a couple of choices. The "old school" way would be to store the data in a tagged union:

struct tagged_data {
    enum { T_INT, T_STR } tag;
    union {
        int x;
        char *y;
    } data;
};

This lets you store either a string or an int, and you set the tag to tell you which one a particular tagged_data item contains. Then (crucially) when you store a string into it, you dynamically allocate the data it points at, so it will remain valid until you explicitly free the data.

Unfortunately, (at least if memory serves) C++11 doesn't support storing non-POD types in a union, so if you went this route, you'd have to use a char * as above, not an actual std::string.

One way to remove (most of) those limitations is to use an inheritance-based model:

class Data {
public:
    virtual ~Data() { }
};


class StringData : public Data {
    std::string content;
public:
    StringData(std::string const &init) : content(init) {}
};

class IntData : public Data {
    int content;
public:
    IntData(std::string const &init) : content(init) {}
};

This is somewhat incomplete, but I think probably enough to give the general idea--you'd have an array (or vector) of pointers to the base class. To insert data, you'd create a StringData or IntData object (allocating it dynamically) and then store its address into the collection of Data *. When you need to get one back, you use dynamic_cast (among other things) to figure out which one it started as, and get back to that type safely. All somewhat ugly, but it does work.

Even with C++11, you can use a template-based solution. For example, Boost::variant, can do this job quite nicely. This will provide an overloaded constructor and value semantics, so you could do something like:

boost::variant<int, std::string> some_object("input string");

In other words, it's pretty what you'd get if you spent the time and effort necessary to finish the inheritance-based code outlined above--except that it's dramatically cleaner, since it gets rid of the requirement to store a pointer to the base class, use dynamic_cast to retrieve an object of the correct type, and so on. In short, it's the right solution to the problem (until/unless you can upgrade to a newer compiler, and use std::variant instead).

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • let consider in main(...), after returning from readData(...), void* myData can point-to more that just int and string. For example if I have a switch case in readData with 100 possible type void*&data can point-to. Would I do an inheritance for all those 100 types? Is there not a more generic approach to this problem? – Gaetan Jun 20 '18 at 09:43
  • @Gaetan: Sure there is--but if you want generic in C++, you use templates. That's exactly what they're for. – Jerry Coffin Jun 20 '18 at 14:10
  • yes, that is why i wrote the template structure dataStruct. Now the point is, how in this line vec.push_back({ id, &myData> }); do I know/get and pass the type myData is pointing-to? – Gaetan Jun 20 '18 at 14:59
  • @Gaetan: You typically assign an integer to each type, and store the integer, so when you read it back in, you can determine the type of the rest of the data from that integer. General style shown here: https://stackoverflow.com/q/3741682/179910 (and litb's answer may be quite useful as well). – Jerry Coffin Jun 20 '18 at 15:44
-2

Apart from the problem in given code described in comments/replies. I am trying to answer your question

 vec.push_back(id, &myData<?>); //how can I set the template param to be the type  myData point to? 

Before that you need to modify vec definition as following

vector<dataStruct<void>> vec;

Now you can simple push element in vector

vec.push_back({id, &mydata, bb});

i have tried to modify your code so that it can work

#include<iostream>
#include<vector>

using namespace std;

template<typename T>
struct dataStruct
{
        int id;
        T** data;
        bool mybool;
};

void readData(int &ID, void*& data, bool& mybool)
{
        if (mybool)
        {
                data = new string("bla");
        }
        else
        {
                int b = 0;
                data = &b;
        }
}

int main ()
{
        void* mydata = nullptr;
        vector<dataStruct<void>> vec;
        bool bb = false;
        for (int id = 0; id < 5; id++)
        {
                if (id%2) bb = true;
                readData(id, mydata, bb);
                vec.push_back({id, &mydata, bb});
        }



  }
SaurabhS
  • 633
  • 1
  • 7
  • 18
  • This does not solve the problem and is wrong/not useful because all you are doing is storing pointers to `mydata` in the vector. Each entry in the vector will store the exact same `data` value because `&mydata` (the location of the local variable `mydata`) never changes in the loop. – Max Langhof Jun 20 '18 at 15:22