Name resolution issue

K

Ken Camann

I think I understand why the following is happening (it compiles in
both MSVC++ and gcc) I just wanted to be sure I understand the
language issues at work here. I define this very simple class in
classa.h:

template <typename T> class B; //forward declaration of B

template <typename T>
class A
{
public:
A() : x(0);
T getX() const {return x;}
void setX(T _x) {x = _x;}

changeY(B<T>& bref)
{
bref.setY(bref.getX() + 1);
}

private:
T x;
};

so I have a class "A" which stores a variable x. Similarly, I have a
symmetric class B which is almost the same but stores a variable y,
has a getY, setY, and changeX(A<T>& aref), forward declares A, etc.,
and is defined in classb.h

Now I can use these in the expected way, by #including classa and
classb in main.cpp, and increasing both x and y by 1, but indirectly
(calling changeY on A with the B instance and changeX on B with the A
instance).

My initial thought was that this would not work. This is what I
thought would happen:

1. Compiler sees #include "classa.h", includes it
2. Compiler sees "template <typename T> class B" forward declaration
3. Compiler parses declaration of class A. It's ok that it finds the
symbol B<T>& because of the forward declaration
4. Compiler parses definition of member function A<T>::changeY. This
references the names B<T>::getY and B<T>::setY. Compiler complains
because it while it recognizes reference types involving B<T> (like
B<T>* or B<T>&) because of the forward declaration, member names in
B<T> are still undefined symbols because it hasn't read the class B
declaration yet (to be included in the future, by the inclusion of
classb.h)

And yet the code worked. I have a guess as to why this is, I was
hoping someone could tell me if this is right, and if this is standard
C++ behavior:

This is OK because types are checked, but definitions are not
generated until template instantiation takes place. By the time that
happens (below both #include directives), both the declarations and
definitions of A and B have been encountered in the translation unit
main.cpp, so everything will work. This means you don't need to worry
about anything as long as you include all the h files at the top of
a .cpp translation unit; by the time your actual .cpp code is reached,
everything can see the entire definition/declaration of everything
else.

It also means that specializations of A (e.g., A<int>) cannot
reference specializations of B like B<int>, since this looks like an
instantiated type to the compiler. Instead, you must work with a
member template version of changeX, templatized with T, and accepting
generic type B<T>&. The same is true for the definition of changeY in
B<int>. At instantiation time, the correct thing will still happen,
since you will actually pass the method a B<int>&, and the B<int>
specialization has now been seen by the compiler during the #include
phase.

Is this basically correct? I tried to look this up myself in "The C++
Programming Language" but I couldn't find it (I wasn't sure what the
name of this little detail is called in C++ parlance).

Thanks,
Ken
 
?

=?iso-8859-1?q?Erik_Wikstr=F6m?=

I think I understand why the following is happening (it compiles in
both MSVC++ and gcc) I just wanted to be sure I understand the
language issues at work here. I define this very simple class in
classa.h:

template <typename T> class B; //forward declaration of B

template <typename T>
class A
{
public:
A() : x(0);
T getX() const {return x;}
void setX(T _x) {x = _x;}

changeY(B<T>& bref)
{
bref.setY(bref.getX() + 1);
}

private:
T x;

};

so I have a class "A" which stores a variable x. Similarly, I have a
symmetric class B which is almost the same but stores a variable y,
has a getY, setY, and changeX(A<T>& aref), forward declares A, etc.,
and is defined in classb.h

Now I can use these in the expected way, by #including classa and
classb in main.cpp, and increasing both x and y by 1, but indirectly
(calling changeY on A with the B instance and changeX on B with the A
instance).

My initial thought was that this would not work. This is what I
thought would happen:

1. Compiler sees #include "classa.h", includes it
2. Compiler sees "template <typename T> class B" forward declaration
3. Compiler parses declaration of class A. It's ok that it finds the
symbol B<T>& because of the forward declaration
4. Compiler parses definition of member function A<T>::changeY. This
references the names B<T>::getY and B<T>::setY. Compiler complains
because it while it recognizes reference types involving B<T> (like
B<T>* or B<T>&) because of the forward declaration, member names in
B<T> are still undefined symbols because it hasn't read the class B
declaration yet (to be included in the future, by the inclusion of
classb.h)

And yet the code worked. I have a guess as to why this is, I was
hoping someone could tell me if this is right, and if this is standard
C++ behavior:

This is OK because types are checked, but definitions are not
generated until template instantiation takes place. By the time that
happens (below both #include directives), both the declarations and
definitions of A and B have been encountered in the translation unit
main.cpp, so everything will work. This means you don't need to worry
about anything as long as you include all the h files at the top of
a .cpp translation unit; by the time your actual .cpp code is reached,
everything can see the entire definition/declaration of everything
else.

It also means that specializations of A (e.g., A<int>) cannot
reference specializations of B like B<int>, since this looks like an
instantiated type to the compiler. Instead, you must work with a
member template version of changeX, templatized with T, and accepting
generic type B<T>&. The same is true for the definition of changeY in
B<int>. At instantiation time, the correct thing will still happen,
since you will actually pass the method a B<int>&, and the B<int>
specialization has now been seen by the compiler during the #include
phase.

Is this basically correct? I tried to look this up myself in "The C++
Programming Language" but I couldn't find it (I wasn't sure what the
name of this little detail is called in C++ parlance).

The sad truth is that many compilers don't bother with templates until
they are instantiated, which means that you can write lots of classes
and everything compiles fine until you try to instantiate one of them.
Or you can successfully instantiate a class and use it but when you
later try to use one of it's methods for the first time it all breaks
down.

I didn't read your code carefully enough to see if it will work or not
but in my experience you'll never know until you try.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top