This is not true in C++. The C++ compilers these days are fully capable of devirtualizing function calls by specializing (cloning) functions, inlining and inter procedual optimizations. It does not always do that of course, since the compiler may not always have the full picture.
It’s also not true for julia. When you write code like f(a, b) = g(a, b)
there’s absolutely no way to figure out what method of g
it is calling, just like C++. You always need context.
The only difference here that you need to get used to is that in julia, doing f(a, b) = g(a, b); f(x, y)
is never going to be differernt from g(x, y)
, unlike C++, since the signature of f
has absolutely no effect on the dispatch, and so there’s no point doing that in an example. Also, for the same reason, since the signature of the function generally have everything to do with dispatch in C++, people will just write the example like f(a, b) = g(a, b)
without the f(x, y)
which will look like nothing is known about the dispatch even though in both cases there’s no difference.
To make my point more clear. A C++ example,
// ----
struct Base {
virtual int g() { return 1; }
};
struct Derived: Base {
virtual int g() { return 2; }
};
int f(Base &b)
{
return b.g();
}
// ----
int main()
{
Derived d;
return f(d);
}
The part in between // ----
is what you are going to see in a typical C++ example and since context is removed it’ll appear as if the dispatch is unknown, even though the value is always generated with a known type. And to proof the point, if you compile that code with either gcc or clang at O2 or higher, there’s no virtual function call at runtime. (Sure this is a simple example, but I’ll bet you that most C++ examples you’ll find are easy for compilers these days to optimize given the same kind of context you see in julia examples)
Now compare that to a julia example,
abstract type BaseT end
struct Derived <: BaseT end
f(::BaseT) = 1
f(::Derived) = 2
g(d::BaseT) = f(d)
g(Derived())
This will probably be shown as a whole in an example simply because there’s nothing in g(d::BaseT) = f(d)
that tells you what this code is doing, the ::BaseT
there never have any effect on the f(d)
, unlike in C++. Because of that, the context is generally included (since that’s the only way you get any idea about what object the code is running on) and it’ll look like the dispatch is known when the code is written, even though it’s every bit as uncertain as the c++ case since you can call g
with a yet unknown derived type from BaseT
.
(And another reason you’ll usually see context in julia example is that it makes the code runnable from the REPL, a fully runnable program in a C++ example is a much taller order although cppreference.com does a very good job and because of that, the virtual function example at virtual function specifier - cppreference.com can be compiled by a mordern compiler to have no ruutime dispatch)
Basically in both case, without the caller of g
, it’s equally uncertain and with it it’s equally certain. The way C++ code is deployed typically does make one case more relavent then another but that has no effect on the spec of the language. JIT for C++ also exist which should be able to do specialization just like julia and writing a dummer julia compiler to make things looks more uncertain is trivial. That’s why the implementation difference is not very important…