Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

bogus "use of local variable with automatic storage from containing function" with c++ template?

Tags:

c++

gcc

templates

Following code does not compile in g++7.2.0

template <class Internal>
class Request {
    int content = 0;
public:
    friend void setContent(int i, void *voidptr) {
        Request<Internal> *ptr = (Request<int>*)voidptr;
        ptr->content = i;
    }
    int getContent() {return content;}
};

int main() {
    Request<int> req;
    setContent(4, &req);
    return req.getContent();
}

With error

test.cpp: In instantiation of ‘void setContent(int, void*)’:
test.cpp:14:23:   required from here
test.cpp:7:14: error: use of local variable with automatic storage from containing function
         ptr->content = i;
         ~~~~~^~~~~~~
test.cpp:6:28: note: ‘Request<Internal>* ptr’ declared here
         Request<Internal> *ptr = (Request<int>*)voidptr;

I don't understand what is wrong with this (apart from being stupid example). Clang 4.0.1 seems to accept it and I am pretty sure it compiled under g++ 5.4 Also: if I remove all templates, this compiles ok.

Is this bug in compiler or I violate some rule I don't know?

EDIT It seems it stopped working beginning with gcc 7.x https://godbolt.org/g/D6gqcF

like image 713
MateuszL Avatar asked Jan 30 '18 15:01

MateuszL


2 Answers

I don't know whether GCC is legitimately balking at this construction, but it seems like the total lack of Request<Internal> in the type of setContent is confusing it. The only reason you can refer to Request<Internal> is the injected class name, that comes from the scope of the definition.

template <class Internal>
class Request {
    int content = 0;
public:
    friend void setContent(int i, void *voidptr); // shouldn't make a difference
    int getContent() {return content;}
};

void setContent(int i, void * voidptr)
{
    // Where does Internal come from?
    Request<Internal> *ptr = (Request<Internal>*)voidptr; 
    ptr->content = i;
}

I don't think you want a template here, because if you instantiate it with a second type you violate the one definition rule.

class RequestBase {
    int content = 0;
public:
    virtual ~RequestBase() = default;
    friend void setContent(int i, RequestBase * ptr) {
        ptr->content = i;
    }
    int getContent() {return content;}
};

If there are other members of your class, which use Internal, you can add them with a template subclass

template <class Internal>
class Request : public RequestBase {
    // members involving Internal
};

The other alternative is that you use Internal in the arguments to setContent

template <class Internal>
class Request {
    Internal content = 0;
public:
    friend void setContent(Internal i, void *voidptr){
        Request<Internal> *ptr = (Request<Internal>*)voidptr;
        ptr->content = i;
    }
    Internal getContent() {return content;}
};

or

template <class Internal>
class Request {
    int content = 0;
public:
    friend void setContent(int i, Request<Internal> *ptr) {
        ptr->content = i;
    }
    int getContent() {return content;}
};

note that you can still bind void setContent(int, Request<Internal> *) to a void (*)(int, void *) function pointer etc

like image 94
Caleth Avatar answered Nov 10 '22 09:11

Caleth


This one looks genuinely tricky. Let's look at the line again:

Request<Internal> *ptr = (Request<int>*)voidptr;

Those are two different types. How do you convert between one type and another? Well, the obvious way would be a derived-to-base conversion.

Now, you may say that Request<int> is not a derived class. If we look at the whole program, that's true. But in the first phase of template compilation, the compiler hasn't seen any template specializations yet. There may still be a specialization of Request<int> that could introduce a base class later down the road (!)

I'd have to grab appropriate C++ standards to check is something subtle changed in this area, but from an engineering point is comes as no surprise when such code proves fragile in the face of minor compiler changes.

like image 1
MSalters Avatar answered Nov 10 '22 09:11

MSalters