template madness: Bug or misunderstanding scoping rules?

I am working on a project that stopped compiling properly after an unwanted compiler update (long story). I am using the latest Visual Studio C++ compiler (free edition).

I figured out how to get it to compile again but I do not understand why I had to do what I did in order to make it work again.

I extracted and simplified the code so it can be reproduced and easily identified.

I tracked it down to one issue: The scoping rules changed on me after the compiler update.

Below is the old code I converted (in namespace OldCode).
Note how I can access the member variable 'x' and 'y' directly.
Following is the template code (in namespace NewCode).
I can no longer access 'x' or 'y' directly.
Now I must use this-> or baseclass:: in order to access it.

remember: This compiled clean before. After the update it does not.

Were some rules tightened? Was the compiler not up to spec before? Or should the code still work and this is some sort of bug? Or am I not understanding what is going on here?

The line of code is just after the comment:
//****** What's going on here?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
namespace OldCode {
	// This is the code I converted to use a template.
	// Note that I can refer to 'x' and 'y' directly
	// in Point::operator= without using this->

	struct _tagPOINT
	{
		int x;
		int y;

		_tagPOINT(int a, int b) :x(a), y(b) {}
	};

	class Point : public _tagPOINT
	{
	public:
		Point() :_tagPOINT(0, 0) {}
		Point(int a, int b) :_tagPOINT(a, b) {}
		Point(const Point & p) : _tagPOINT(p.x, p.y) {}

		Point & operator=(const Point & rhs)
			{ x = rhs.x; y = rhs.y; return *this; }
	};
} // namespace OldCode

namespace NewCode
{
	// Here is the new templated form of the above, complete
	// with the error I do not understand.
	// The error is in Point::operator=

	template<class T>
	struct _tagPOINT
	{
		T    x;
		T    y;
		_tagPOINT(T a, T b) :x(a), y(b) {}
	};

	template<class T>
	class Point : public _tagPOINT<T>
	{
	public:

		Point() :_tagPOINT<T>(0, 0) {}
		Point(T a, T b) :_tagPOINT<T>(a, b) {}
		Point(const Point & p) : _tagPOINT<T>(p.x, p.y) {}

		Point & operator=(const Point & rhs)
		{

//****** What's going on here?

			x = rhs.x; // Shouldn't this line work?

// The compiler complains 'x': identifier not found.
// It works in the non-templated OldCode
//******

			this->x = rhs.x; // This line compiles but I
			// assumed this should be equivalent to the above

			_tagPOINT<T>::x = rhs.x; // compiles: Direct reference
			// to 'x' in tagPOINT

			this->y = rhs.y;
			return *this;
		}
	};
} // namespace NewCode

int main()
{
	OldCode::Point OldPoint;

	// Try all 3 template constructors
	NewCode::Point<int> p1;
	NewCode::Point<int> p2(0,0);
	NewCode::Point<int> p3(p1);

	p1 = p2;
}


PS: I hope this displays properly. For some reason the preview feature doesn't work for me in Microsoft Edge or Chrome. I'm not having any luck :(
gcc is doing the same thing as your compiler. It likes the OldCode but doesn't like the NewCode. "error: ‘x’ was not declared in this scope"

Are you saying that the template version worked before the compiler update?

Anyway, the only difference between the two classes is that one is a template. Notice that you don't even need to call operator= since the error is being flagged on the first pass (templates are first "compiled" without instantiation just to look for errors; that's where it's flagging this error). In fact, gcc still gives the error even if main is empty, so you don't even need to instantiate the class.

Here's a "simplified" version. I compressed it visually so people can easily convince themselves that the two classes are the same except for one being a template.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct OldPointBase {
    int x, y;
    OldPointBase(int a, int b) : x(a), y(b) {}
};

class OldPoint : public OldPointBase {
public:
    OldPoint(int a=0, int b=0) : OldPointBase(a, b) {}
    OldPoint& operator=(const OldPoint& rhs) {
        x = rhs.x;
        y = rhs.y;
        return *this;
    }
};

template<class T>
struct PointBase {
    T x, y;
    PointBase(T a, T b) : x(a), y(b) {}
};

template<class T>
class Point : public PointBase<T> {
public:
    Point(T a=0, T b=0) : PointBase<T>(a, b) {}
    Point& operator=(const Point& rhs) {
        x = rhs.x;  // Works: this->x = rhs.x; or PointBase<T>::x = rhs.x;
        y = rhs.y;
        return *this;
    }
};

int main() {
    OldPoint o1;
    Point<int> p1;
}

Last edited on
x = rhs.x; // Shouldn't this line work?

It shouldn't. it is invalid C++.

Here's a discussion on stackoverflow: https://stackoverflow.com/questions/4643074/why-do-i-have-to-access-template-base-class-members-through-the-this-pointer/4643295

Last edited on
Thanks for the simplification. It does show up better.

You mentioned: "Notice that you don't even need to call operator= since the error is being flagged on the first pass..."

This occurs for me as well using MSVC. I updated my compiler, recompiled my source code and immediately got this error. No code changes at all.

You asked: "Are you saying that the template version worked before the compiler update?"

Yes.

My project grew to 5 sub-projects. I wrote this weeks ago - at the beginning.

I was just wondering if it is a bug but I guess gcc wouldn't coincidentally have the same bug.

This makes me wonder about what are the scoping rules and why this->x is not the same as x. I could understand if a code change causes ambiguity - not disappearing from the scope.
Cubbi's link explains it. MSVC didn't used to have 2-phase compilation of templates. They must have added it as part of the update. That and a bunch of wacky rules gives your answer.
Thanks Cubbi. I skimmed thru:
https://stackoverflow.com/questions/4643074/why-do-i-have-to-access-template-base-class-members-through-the-this-pointer/4643295

There is a good detailed explanation regarding this issue. I was just never aware of this. So yea, I guess the answer to my topic is that I misunderstood the scoping rules for templates.
It boils down to:
C++11 Draft Standard:
14.6.2 Dependent names
3. In the definition of a class or class template, if a base class depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.

Topic archived. No new replies allowed.