views:

141

answers:

4

How does the C++ compiler decide which function/method to call if there are multiple possibilities? In my specific case I have the standard free function of the C++ Run time and I also have a templated free variant, like this:

// The definitions of the C++ Run Time Library (from memory.h)
extern malloc(size_t s);
extern void free(void *p);

// Our own memory management functions
extern void *OurMalloc(size_t s);
extern void OurFree(void *p);

// Own variants to overrule malloc and free (instead of using #define)
template<typename T>
void *malloc(T t)
{
return OurMalloc(t);
}

template<typename T>
void free(T *t)
{
OurFree(t);
}

I tested this using the following code:

void main(void)
{
void *p = malloc(10);
free(p);
}

If I compile and run this, it seems that the call to malloc is correctly replaced by the templated variant. So far, so good.

However, the call to free is not replaced by the templated variant, and the standard C++ function is still called.

What rules does the C++ compiler use to decide which variant to give priority? Is this related to the Koenig-lookup rules?

Note: I tried this alternative because using #define does not solve the problem (see question http://stackoverflow.com/questions/2319381).

+8  A: 

Overload resolution is quite complicated in general.

In your case, it is quite easy: a function template is not considered if there is an exact match. For free it is the case (the standard free takes a void*), for malloc it isn't (the standard malloc takes a size_t, you are passing an int and size_t can't be a typedef for int -- size_t is unsigned). If you call free with a type other than void*, it should instantiate your template.

Running:

#include <iostream>

void* ml(size_t s)
{
    std::cout << "ml(size_t)\n";
}

void fr(void *p)
{
    std::cout << "fr(void*)\n";
}

template<typename T>
void* ml(T t)
{
    std::cout << "ml<" << typeid(T).name() << ">(T)\n";
}

template<typename T>
void fr(T *t)
{
    std::cout << "fr<" << typeid(T).name() << ">(T*)\n";
}

int main()
{
    void* p1 = ml((size_t)10);
    fr(p1);
    int* p2 = (int*)ml(10);
    fr(p2);
    return 0;
}

I get

ml(size_t)
fr(void*)
ml<i>(T)
fr<i>(T*)

and i is what returns typeid(int).name()

AProgrammer
Thanks for the explanation, although I hoped there would be an answer that allows me to decently override malloc and free.
Patrick
I'm not sure what you want to achieve. If you want to use your own implementation of malloc/free, this is usually supported by standard library implementations and achievable at link time. But you have to check your implementation(s) for information about that.
AProgrammer
+3  A: 

For your particular issue about malloc and free, the problem is that in your call to malloc:

void *p = malloc(10);

the parameter 10 is typed as an int, while the signature for the runtime's malloc() calls for an unsigned argument. Since there's not an exact match, the compiler prefers the templated malloc where it can create an exact match.

When you call:

free(p);

the type of p is void* which does exactly match the runtime's signature for free() so the compiler doesn't bother using the templated free.

Michael Burr
+2  A: 

It is not possible to "replace" the standard malloc using this technique. Other answers have already explained that because you are using a signed value as an argument in malloc call, your templated version happens to "win" over the standard one because the standard one expects an unsigned argument.

To better illustrate this I just wanted to add that if you supply either an unsigned int or unsigned long argument in your malloc call

void *p1 = malloc(10u);
void *p2 = malloc(10ul);

and you'll notice that in one of these calls your templated version of malloc also doesn't "work" anymore and the standard one is called instead, since it is a better match for the argument (provided that on your platform size_t is defined as either unsigned int or unsigned long)

AndreyT
Andrey, that depend on size_t being unsigned. Here, size_t is unsigned long, so you'd need 10ul to get the match -- that's why I casted to size_t in my answer instead of using the suffix.
AProgrammer
@AProgrammer: You're right. I'll update my answer.
AndreyT
A: 

Not answering the question you asked, but what it seems like you're trying to do:

If it's available on your system, you can use LD_PRELOAD to preload a .so library you build that has your versions of malloc and free. Then they will definitely be called instead of the standard versions.

Mark B