Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Overloading global function in a class

17 views
Skip to first unread message

Steve Keller

unread,
Feb 24, 2017, 12:52:10 PM2/24/17
to
For a simple interpreter I wrote a class

class Stmt {
public:
virtual void exec() const = 0;
...
};

and several statement classes derived from that. In these classes I
have a number of occurrences of code like the following:

class FooStmt : public Stmt {
Stmt *s;
public:
virtual void exec() const {
if (s)
s->exec();
}
};

Therefore, I added a wrapper

void exec(const Stmt *s) {
if (s)
s->exec();
}

However, in order to be able to call exec(const Stmt *) from inside
FooStmt, I either need to

a) make this wrapper a static member function of class Stmt

b) if the wrapper is outside of class Stmt, I have to call it using

::exec(s)

instead of only exec(s).

c) rename the wrapper to something different than exec.

It seems the compiler, when looking for an exec(const Stmt *)
function, first looks for any exec(...) function in FooStmt and if it
finds one (in this case the exec() member function without parameters)
it does not continue to look for a matching exec(const Stmt *) outside
of FooStmt. But why does is behave that way?

I don't like any of these solutions because a) makes the wrapper
visible in the header file and b) and c) look cumbersome.

Are there other suggestions?

Steve

Paavo Helde

unread,
Feb 24, 2017, 1:53:20 PM2/24/17
to
On 24.02.2017 19:52, Steve Keller wrote:
> For a simple interpreter I wrote a class
>
> class Stmt {
> public:
> virtual void exec() const = 0;
> ...
> };
>
> and several statement classes derived from that. In these classes I
> have a number of occurrences of code like the following:
>
> class FooStmt : public Stmt {
> Stmt *s;

I assume you have taken care to consider why you can and need to use a
raw pointer here instead of some standard smart pointer.


> public:
> virtual void exec() const {
> if (s)
> s->exec();
> }
> };
>
> Therefore, I added a wrapper
>
> void exec(const Stmt *s) {
> if (s)
> s->exec();
> }
>
> However, in order to be able to call exec(const Stmt *) from inside
> FooStmt, I either need to
>
> a) make this wrapper a static member function of class Stmt
>
> b) if the wrapper is outside of class Stmt, I have to call it using
>
> ::exec(s)
>
> instead of only exec(s).
>
> c) rename the wrapper to something different than exec.
>
> It seems the compiler, when looking for an exec(const Stmt *)
> function, first looks for any exec(...) function in FooStmt and if it
> finds one (in this case the exec() member function without parameters)
> it does not continue to look for a matching exec(const Stmt *) outside
> of FooStmt. But why does is behave that way?

It's a general rule in C++ to use the matching identifier from the
closest scope (class scope in this case). Otherwise you could not write
things like

#include <complex.h>

int foo(int arg) {
return arg + 1; // ambiguity! is this the local parameter
// or the arg() function from <complex.h>?
}


> I don't like any of these solutions because a) makes the wrapper
> visible in the header file and b) and c) look cumbersome.

Basically you want to have a special kind of pointer where dereferencing
null is ignored. In C++ one writes a new class for such things:

class ExecPtr {
const Stmt* s;
// ...
public:
void exec() const {
if (s) {
s->exec();
}
}
};

class FooStmt : public Stmt {
ExecPtr s;
public:
virtual void exec() const {
s.exec();
}
};




Adam C. Emerson

unread,
Feb 24, 2017, 3:16:04 PM2/24/17
to
On 2017-02-24, Steve Keller <kel...@no.invalid> wrote:
> For a simple interpreter I wrote a class
>
> class Stmt {
> public:
> virtual void exec() const = 0;
> ...
> };
>
> and several statement classes derived from that. In these classes I
> have a number of occurrences of code like the following:
>
> class FooStmt : public Stmt {
> Stmt *s;
> public:
> virtual void exec() const {
> if (s)
> s->exec();
> }
> };
[snip]

This design looks very strange to me. Why do you both inherit from
Stmt and include a pointer to Stmt?

Cholo Lennon

unread,
Feb 24, 2017, 3:42:05 PM2/24/17
to
Because, at 1st glance, it seems like the OP is using the decorator pattern.

--
Cholo Lennon
Bs.As.
ARG

Steve Keller

unread,
Feb 24, 2017, 7:10:30 PM2/24/17
to
Paavo Helde <myfir...@osa.pri.ee> writes:

> It's a general rule in C++ to use the matching identifier from the
> closest scope (class scope in this case). Otherwise you could not
> write things like
>
> #include <complex.h>
>
> int foo(int arg) {
> return arg + 1; // ambiguity! is this the local parameter
> // or the arg() function from <complex.h>?
> }

Yes, of course. But since in my code the call to exec(s) with Stmt *s
doesn't match the definition of FooStmt::exec() I wouldn't count this
as a match and continue matching in a wider scope and would find the
actually matching global exec(const Stmt *).

> Basically you want to have a special kind of pointer where
> dereferencing null is ignored. In C++ one writes a new class for such
> things:
>
> class ExecPtr {
> const Stmt* s;
> // ...
> public:
> void exec() const {
> if (s) {
> s->exec();
> }
> }
> };
>
> class FooStmt : public Stmt {
> ExecPtr s;
> public:
> virtual void exec() const {
> s.exec();
> }
> };

Hm, this looks like an interesting solution. I'll consider this. But
for consistency I'd have to do it for class Expr {} with Expr::eval()
also, although I don't have the same problem there. What I dislike a
little is that in s.exec() the member s doesn't really look like a
pointer anymore.

Steve

Steve Keller

unread,
Feb 24, 2017, 7:11:05 PM2/24/17
to
Actually, there is no FooStmt but WhileStmt, IfStmt. ForStmt, etc.
which *are* statements and some contain other statements. There's
also an abstract base class Expr with a number of subclasses, and my
statements look like this

class WhileStmt : public Stmt {
Expr *cond;
Stmt *body;
public:
...
};
class IfStmt : public Stmt {
Expr *cond;
Stmt *s1, *s2;
public:
...
};
class ForStmt : public Stmt {
Expr *init, *cond, *iter;
Stmt *body;
public:
...
};
class ExprStmt : public Stmt {
Expr *expr;
public:
...
};

I think that is quite straight forward and natural.

Steve

Paavo Helde

unread,
Feb 24, 2017, 8:25:35 PM2/24/17
to
On 25.02.2017 2:10, Steve Keller wrote:
> Paavo Helde <myfir...@osa.pri.ee> writes:
>
>> It's a general rule in C++ to use the matching identifier from the
>> closest scope (class scope in this case). Otherwise you could not
>> write things like
>>
>> #include <complex.h>
>>
>> int foo(int arg) {
>> return arg + 1; // ambiguity! is this the local parameter
>> // or the arg() function from <complex.h>?
>> }
>
> Yes, of course. But since in my code the call to exec(s) with Stmt *s
> doesn't match the definition of FooStmt::exec() I wouldn't count this
> as a match and continue matching in a wider scope and would find the
> actually matching global exec(const Stmt *).

Yes, this might seem like a good idea, but over the years I have learned
the hard way that trying to be too clever is about the worst thing a
language can do. The reason is that if the language is too clever then
the programmer is not able any more to figure out how it works and thus
it loses control over how to achieve that it wants.

C++ already has function overloading, Koenig lookup and other things
where it has arguably gone too clever, there is no need to make it even
more "cleverer".

>> Basically you want to have a special kind of pointer where
>> dereferencing null is ignored. In C++ one writes a new class for such
>> things:
>>
>> class ExecPtr {
>> const Stmt* s;
>> // ...
>> public:
>> void exec() const {
>> if (s) {
>> s->exec();
>> }
>> }
>> };
>>
>> class FooStmt : public Stmt {
>> ExecPtr s;
>> public:
>> virtual void exec() const {
>> s.exec();
>> }
>> };
>
> Hm, this looks like an interesting solution. I'll consider this. But
> for consistency I'd have to do it for class Expr {} with Expr::eval()
> also, although I don't have the same problem there. What I dislike a
> little is that in s.exec() the member s doesn't really look like a
> pointer anymore.

You can make it look like s->exec() with some more effort, though by
some reason I suspect you are wasting your pedantry in wrong areas.

There are no abstract ideals to which your code should conform.

Instead, programming is an engineering discipline, meaning everything
serves a purpose. Consistency of the code and markup is needed only for
the maintainer programmer (including yourself 6 months ahead) to
understand the program so he can easily maintain or refactor it. In this
sense, writing some decent unit tests is probably much more helpful than
a minor stylistic consistency.


0 new messages