Templates, inheritance and variable visibility

A

ajhietanen

Hi everybody,

I have a following class structure, where B inherits A and specifies
the other template parameter.

template <typename T,typename V>
class A {
protected:
T a_;
};

template <typename V>
class B: public A<int,V>{
public:
B(){a_=2;} // <-- error: ‘a_’ was not declared in this scope
};


The problem is that compiler does not find the declaration of a_. The
program compiles if I write
A<int,V>::a_ = 2;
or I specify both of the template parameters. The compiler is g++
(gcc) 4.2.4

Why is this? And is there a way to use the variable a_ without the
whole specification, e.g., with keyword using?

Thank you,
Ari
 
I

Ian Collins

Hi everybody,

I have a following class structure, where B inherits A and specifies
the other template parameter.

template <typename T,typename V>
class A {
protected:
T a_;
};

template <typename V>
class B: public A<int,V>{
public:
B(){a_=2;} // <-- error: ‘a_’ was not declared in this scope
};


The problem is that compiler does not find the declaration of a_. The
program compiles if I write
A<int,V>::a_ = 2;
or I specify both of the template parameters. The compiler is g++
(gcc) 4.2.4

Why is this?

Have a google for two phase lookup.
And is there a way to use the variable a_ without the
whole specification, e.g., with keyword using?

using A<int,V>::a_;

will work, so will

this->a_;
 
A

Alf P. Steinbach

* (e-mail address removed):
Hi everybody,

I have a following class structure, where B inherits A and specifies
the other template parameter.

template <typename T,typename V>
class A {
protected:
T a_;
};

template <typename V>
class B: public A<int,V>{
public:
B(){a_=2;} // <-- error: ‘a_’ was not declared in this scope
};


The problem is that compiler does not find the declaration of a_. The
program compiles if I write
A<int,V>::a_ = 2;
or I specify both of the template parameters. The compiler is g++
(gcc) 4.2.4

Why is this?

The definition or existence of 'a_' depends on template parameter.

You have to tell the compiler in some way, by qualification or 'using', that it
comes from the base class and is not from an enclosing scope.

Exactly why that rule was chosen instead of the for C++98 more reasonable of
having to qualify use of enclosing scope names is a mystery.

And is there a way to use the variable a_ without the
whole specification, e.g., with keyword using?

Yes, exactly.


Cheers & hth.,

- Alf
 
I

Ian Collins

Alf said:
The definition or existence of 'a_' depends on template parameter.

You have to tell the compiler in some way, by qualification or 'using',
that it comes from the base class and is not from an enclosing scope.

Exactly why that rule was chosen instead of the for C++98 more
reasonable of having to qualify use of enclosing scope names is a mystery.

To you and me both. Although James has cited some corner cases where it
does make sense, I'm still not convinced. It leads to some pretty ugly
and unnecessarily verbose code.
 
I

Ian Collins

Jeff said:
How would you qualify a name as "from the enclosing scope?" Within the
current language, I think you'd have to hard-code the entire sequence of
namespaces for each identifier, from global scope on down. If you so
much as wrote std::cout, the compiler would get confused, since it would
have to assume std was defined in the base class.


At the time the derived class is defined, the base class hasn't
necessarily been seen yet. There is no way for the compiler (or the
human reader) to know whether the base defines a_, no matter what the
base class's primary template defines. The enclosing scope, OTOH, is
already available.

Then how comes older compilers and those that choose not to use two
phase lookup mange?

Everything is know at the point of instantiation.
 
A

Alf P. Steinbach

* Jeff Schwab:
How would you qualify a name as "from the enclosing scope?" Within the
current language, I think you'd have to hard-code the entire sequence of
namespaces for each identifier, from global scope on down. If you so
much as wrote std::cout, the compiler would get confused, since it would
have to assume std was defined in the base class.

Uhm, there's both a good point and a bad point (or un-point).

The good point: what to do about about e.g. std::cout.

The bad point: imagining an impractical non-solution and using that as argument.
The possible existence of impractical ways of doing X doesn't imply that X is
impractical. So in terms of reasoning and argumentation it's a fallacy, even
when assuming for argument that the purported impractical way is impractical.

Regarding those outer scope names, the practical consideration is just what you
need most often: do you most often need to refer to base class names (such as
types!), or do you most often need to refer to enclosing scope names.

IME one overwhelmingly most often needs to refer to base class names.

And even with nothing more than the facilities of C++98 it's no big deal to write

template< typename T > class Foo
{
void boo() const
{
using namespace std;
cout << "so there" << endl;
}
};

In this context there's generally no need to qualify like ::std because it's
known to be a namespace name so no ambiguity (unless the class, perversely, is
defined in a namespace that contains a namespace called 'std').

But what about, for example, routine result types?

Well, thinking positively about this, namely how to create a practical solution
instead of how to find some hopefully unworkable non-solution, this is a case
for allowing namespace using directives at class scope, like

template< typename T > class Foo
{
using namespace std;
ostream& boo() const
{
cout << "so there" << endl;
return cout;
}
};

One might then argue that scopes simply shouldn't be nested to the degree that
the above becomes impractical. But I think that would be like someone dying of
thirst in a desert, 12 meters from an oasis, because the person reasons that one
should simply not set out on a trip of more than X hundred meters, so better
stop here, at X hundred meters already crawled, and die. Rejecting that
defaitist and irrational mode of thinking, then, what's lacking in C++ is a way
to refer relatively to scopes, like you can do with directories.

More generally, what's lacking is a /general path notation/ for scopes,
considering that such can involve (a) both namespaces and classes, possibly for
full generality also blocks, and (b) relative designations like "..".

I'd like something like

template< typename T > class Foo
{
using namespace $; // Enclosing namespace
void boo() const
{
say( "so there" );
}
};

The idea being that $, not currently used in standard C++ outside string and
character literals, designates a current scope (of some implied kind), and $$
designates a (lexically) parent scope, like "." and ".." for directories.

Hm, neat! :)

Note in this regard: the Euro symbol isn't part of ASCII or original EBCDIC
(actually I don't know about $ and EBCDIC, too lazy to check!), but $ is.

At the time the derived class is defined, the base class hasn't
necessarily been seen yet. There is no way for the compiler (or the
human reader) to know whether the base defines a_, no matter what the
base class's primary template defines. The enclosing scope, OTOH, is
already available.

As pointed out by Ian Collins else-thread, this argument falls on the spears of
existing old compilers that had no problems dealing with the situation.

For there is no more technical problem with an explicitly expressed assumption
than with an implicit assumption.

How could there be?

No matter whether it's explicitly expressed or not, the compiler has to deal
with it. If it can deal with the explicitly expressed assumption, it can deal
with the implicit one. If it can't deal with the assumption in general then C++
template programming is impossible -- but hey, lots of counter-examples! :)

The problem with explicitly expressed assumption is that it's a heck of a lot
more to write, an extreme verbosity and an extreme visual clutter, not to
mention the work in adding in all this stuff, try it out by compiling, adding
more, and so on, including having to write silly extremely redundant things like

typedef FooFoo< U, V > Base;
typedef typename Base::Value Value;
typedef typename Base::Index Index;

and so on.

Talk about COBOL reincarnation!

Or PL/1!


Cheers, & hth.,

- Alf
 
J

James Kanze

Alf P. Steinbach wrote:
To you and me both. Although James has cited some corner
cases where it does make sense, I'm still not convinced. It
leads to some pretty ugly and unnecessarily verbose code.

I'm not sure which cases you're referring to, and if I did, it
was just citing the arguments of others. My own personal
opinion is:

-- The distinction between dependent and non-dependent names is
not necessary. I know the problem it is trying to solve,
but in my experience, it's not a serious problem in well
written code, and the extra complexity makes the cost of the
solution higher than the cost of the problem.

-- *If* you're going to introduce a distinction between
dependent and non-dependent names, then it should be done
explicitly, e.g. with a special declaration at the start of
the template, stating which names are dependent, rather than
depending on subtle issues of syntax (this->a vs. a) to make
the distinction.
 
I

Ian Collins

James said:
I'm not sure which cases you're referring to, and if I did, it
was just citing the arguments of others. My own personal
opinion is:

Sorry, I can't remember the old thread.
-- The distinction between dependent and non-dependent names is
not necessary. I know the problem it is trying to solve,
but in my experience, it's not a serious problem in well
written code, and the extra complexity makes the cost of the
solution higher than the cost of the problem.

So you agree with us?
 
J

James Kanze

Then how comes older compilers and those that choose not to
use two phase lookup mange?
Everything is know at the point of instantiation.

The argument is twofold: the first is that waiting until the
point of instantiation to fully evaluate the template results in
delayed error messages. I think that the actual argument is
that the error messages won't necessarily appear until the
template is used by the client. (IMHO, this is just bullshit.
If you release a template without having tested it, tough shit.
And if you've tested it, you've instantiated it, and seen any
error messages resulting from the instantiation.) The second
argument is that in some cases, the template will pick up
symbols that weren't intended. Something like:

template< typename T >
class C : public T
{
void f( double d1 ) { double d2 = sin( d1 ) ; }
} ;

The template author obviously wants the global sin, but what if
T contains a member function sin. All I can say to this is that
I've not found this to be a problem in actual code, and that
since the introduction of namespaces, that would be std::sin,
anyway, which obviously won't be found in T.
 

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