(I wrote this section after the stuff at the end of the post)
The secret ingredient is: how or by what mechanism the compiler knows how to
pass the foo to the foo function.
The compiler knows it has to pass *a* pointer-to-foo to a (non-static)
member function of foo because there is a pointer-t-foo in the
parameter list. If you write
struct foo
{
void func();
void func2(int i, double d, std::string s);
};
void foo::func() { /* ... */ }
void foo::func2(int i, double d, std::string s) { /* ... */ }
however much you might be tempted to think that you've written a
function, func, that takes zero parameters and a function, func2, that
takes three parameters (an int, a double and a std::string), you
haven't. You've written a function, func, that takes one parameter (a
pointer-to-foo) and a function, func2, that takes four parameters (a
pointer-to-foo, an int, a double and a std::string).
There are only two ways you can call func:
my_pointer_to_foo->func();
my_foo_object.func();
and, again, no matter how much you think you are calling a function
that takes zero arguments, you aren't. What you are doing with those
two statements is actually:
foo::func(my_pointer_to_foo);
foo::func(&my_foo_object);
And there are only two ways you can call func2:
my_pointer_to_foo->func2(my_int, my_double, my_string);
my_foo_object.func2(my_int, my_double, my_string);
which, similarly, is not calling a function that takes three
arguments, no matter how much it looks like it. Really, those two
statements are:
foo::func2(my_pointer_to_foo, my_int, my_double, my_string);
foo::func2(&my_foo_object, my_int, my_double, my_string);
OK, it doesn't. You say it does the
binding at compile time (yes?).
It is known at compile time which pointer-to-foo to pass to the member
function, yes, in exactly the same way as it is known which int,
double and std::string to pass in the case of func 2, and in exactly
the same way it is known at compile time which values to use as the
parameters to any function call, whether a free function or a member
function. It is known because the code you write states which pointer-
to-foo to pass to the member function. If it wasn't apparent from your
code, your code wouldn't compile.
There is no "passing of a foo to a foo func
at runtime" as it is "hard coded" in the compiled code (?).
A pointer-to-foo is passed as parameter to each (non-static) member
function of foo. It has to be known at compiler time *which* pointer-
to-foo is passed.
So with a free
store obj, it does similar and it is enough to know that the pointer to the
foo is "hard coded" (like where you said foo_func(&a_foo)
by the compiler
All that is required is that a pointer-to-foo is passed as a
parameter. Whether that pointer happens to point to a dynamically
allocated object is irrelevant.
and the compiler doesn't care what actual foo is being pointed to. That's it
(!) isn't it?
If I understand you corrently, then yes. The type of the parameter is
pointer-to-foo. If the function is invoked like
my_pointer_to_foo->func();
then the compiler has the necessary pointer-to-foo right there and
that's what it passes to the function. Everything the compiler needs
to make the function call is in that statement (and you will note that
nothing about that statement tells you, me or the compiler anything
about whether the foo object pointed to is dynamically allocated -
conclusion: whether the foo object is dynamically allocated is
irrelevant).
If the function is invoked like
my_foo_object.func();
the compiler's job involves one tiny extra step. The type of the
parameter to pass to func() is pointer-to-foo, but unlike before we
haven't given the compiler a pointer-to-foo, we've given it a foo.
However, it is simplicity itself for the compiler to obtain a pointer-
to-foo from a foo by taking the address of the foo. And that's what it
does.
I was thinking of something less simplistic than the above example. Your
examples show instances where it is easy to comprehend what the compiler is
doing. Somewhere, I thought it would be difficult for the compiler to know
where class objects of a given class were instantiated (or something like
that) so I thought there must be some kind of external mapping of class
objects and their functions.
Perhaps I confused you by first showing an example where a member
function was invoked on an object directly. Maybe I gave you the
impression that the object is what's needed to make a call to a member
function work. Hopefully with the above I've made it clear that what
the compiler needs whenever a member function is called is a pointer.
Without trying to explain any more what I was
thinking, I'm wondering if the examples you show above are how it works ALL
the time (for POD-structs with member functions).
How I've shown it above is how it works all the time, for all non-
static, non-virtual member functions of all classes (not just POD-
structs).
Are you happy that the following two examples are conceptually
identical and that example 2 shows how, in practice, example 1 is
implemented?
// Example 1
struct bar
{
void reset_i();
void set_i(int new_value);
int i;
};
void bar::reset_i() { i = 0; }
void bar::set_i(int new_value)
{
reset_i();
i = new_value;
}
int main()
{
bar* pb1 = new bar;
pb1->set_i(100);
bar b1;
b1.set_i(42);
bar b2;
bar* pb2 = &b2;
pb2->set_i(5);
}
// Example 2
struct bar
{
int i;
};
void bar_reset_i(bar* this_) { this_->i = 0; }
void bar_set_i(bar* this_, int new_value)
{
bar_reset_i(this_);
this_->i = new_value;
}
int main()
{
bar* pb1 = new bar;
bar_set_i(pb1, 100);
bar b1;
bar_set_i(&b1, 42);
bar b2;
bar* pb2 = &b2;
bar_set_i(pb2, 5);
}
Gavin Deane