Let's see. Here are some transformations that a compiler could do (and that, in principle, some compilers actually do):
1) Conditional operator to if, for ease of explanation. This is not something the compiler actually does, just something that is a natural consequence of the intermediate form most compilers use:
Code:
int main()
{
using namespace scratch;
method * m;
if(condition)
m = new trivial;
else
m = new nontrivial;
foo functor(*m);
functor();
delete m;
}
2) Lifting of code into the if branches, if the compiler thinks keeping the cases separate could lead to better code.
Code:
int main()
{
using namespace scratch;
method * m;
if(condition) {
m = new trivial;
foo functor(*m);
functor();
delete m;
} else {
m = new nontrivial;
foo functor(*m);
functor();
delete m;
}
}
3) Complete inlining of the foo object. This is where we leave real C++ syntax.
Code:
int main()
{
using namespace scratch;
method * m;
if(condition) {
m = new trivial;
// Inline storage
{ method& $1_m } functor;
// Inline constructor
$1_m = *m;
// Inline operator()
std::cout << "Functor called." << std::endl;
$1_m.call();
delete m;
} else {
m = new nontrivial;
// Inline storage
{ method& $2_m } functor;
// Inline constructor
$2_m = *m;
// Inline operator()
std::cout << "Functor called." << std::endl;
$2_m.call();
delete m;
}
}
4) Eliminate redundant aliases, and lift the declaration of m into the branches, with updated type information.
Code:
int main()
{
using namespace scratch;
if(condition) {
trivial * m = new trivial;
std::cout << "Functor called." << std::endl;
m->call();
delete m;
} else {
nontrivial * m = new nontrivial;
std::cout << "Functor called." << std::endl;
m->call();
delete m;
}
}
5) Inline statically proven virtual calls.
Code:
int main()
{
using namespace scratch;
if(condition) {
trivial * m = new trivial;
std::cout << "Functor called." << std::endl;
delete m;
} else {
nontrivial * m = new nontrivial;
std::cout << "Functor called." << std::endl;
// Juicy code
delete m;
}
}
6) There you go, the virtual call is gone. The compiler could now go on to inline the constructors in the new expressions and find that trivial's constructor doesn't do anything. It then might (but won't, because no compiler treats allocation-deallocation as side-effect-free) completely omit the allocation of the trivial object. It could, if it proves that nontrivial's constructor has no side effects, reorder the new expression below the cout, lift the common expression of the branches out, and then completely remove the now empty true branch. The end result would then be:
Code:
int main()
{
using namespace scratch;
std::cout << "Functor called." << std::endl;
if(!condition) {
nontrivial * m = new nontrivial;
// Juicy code - or just the call if it's too long
delete m;
}
}
This is theory. What a compiler really does in practice is a QoI issue. However, given the assumptions that allocation does nothing but allocate and order of allocations is unimportant (neither of which holds, but let's assume a really nice system), the program is a valid transformation of your original one. Even without the assumption, you can transform to the point where the virtual call is gone.