4

The following code (which compiles and executes properly, doing what I want) is a minimal example of an oddity I experienced while writing a class to store properties of various types that needed the ability to delete pointers when it no longer knows their types. My solution was to make a Deleter class with a templated function that could have its address taken and stored to delete a specific type. I don't understand why this code works, specifically:

  • Why doesn't it hit the assert?
  • Why/how does it require/use the (seemingly) unrelated specialization?

Code:

#include <iostream>
#include <string>
#include <cassert>

#include <locale> //Just here as an unused class to specialize

using namespace std;

typedef void(*void_voidptr_func_t)(void*);

class ClassWithDestructor {
public:
    ~ClassWithDestructor() {
        cout << "Destroyed\n";
    }
};

class Deleter {
public:
    template <class T>
    static void Delete (T* ptr) {
        assert(0);
    }

    //locale here can be any class
    //it doesn't matter what class it is
    //but if this specialization doesn't exist
    //compile fails
    template <class locale>
    static void Delete(void* ptr) {
        delete (locale*)ptr;
    }
};

void* void_ptr_to_T = NULL;
void_voidptr_func_t T_delete_function = NULL;

template<class T>
void A() {
    T* t = new T;
    void_ptr_to_T = (void*)t;
    T_delete_function = &Deleter::Delete<T>;
}

int main(int argc, char** argv) {
    A<ClassWithDestructor>();
    T_delete_function(void_ptr_to_T);
}

Compiler: MSVC++ 2010, Microsoft Extensions Disabled

Output:

Destroyed

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
0x5f3759df
  • 2,349
  • 1
  • 20
  • 25

2 Answers2

5

This

template <class locale>
static void Delete(void* ptr) {
    delete (locale*)ptr;
}

is not a specialization. This is an overload. A specialization would be like this

template <>
static void Delete(locale* ptr) {
    delete (locale*)ptr;
}

So actually it's equivalent to writing just

template <class T>
static void Delete(void* ptr) {
    delete (T*)ptr;
}

Actually, the behavior you presented is because of the overload resolution on the line

T_delete_function = &Deleter::Delete<T>;

The second overload is more specific as it accepts void* and not T* and type is specified explicitly anyway. So in presence of the mentioned overload it chooses it and it compiles and runs finely. In absence of this more specific overload, compiler calls another appropriate, but more general one which fires the assertion.

You can double check that, i.e. remove the #include <locale> line: compiler will not complain about class locale being undeclared.

unkulunkulu
  • 11,576
  • 2
  • 31
  • 49
3

There are no specializations here: you have two (different) function templates that overload:

template <typename T> void Delete(T*);    // (1)
template <typename T> void Delete(void*); // (2)

&Deleter::Delete<T> could refer to either of the Delete function templates. (2) is selected in your example because its type matches the type of the function pointer to which you are assigning. The type of the function pointer is void(*)(void*); the type of (1) converted to a function pointer is void(*)(T*), which does match unless T = void.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • So this code only works because it's casting void* to locale* and the destructors are in the same places in each class's memory? – 0x5f3759df Jul 30 '11 at 18:36
  • No, in your second function template, `locale` does not name a type; it names the type parameter to the function. You could replace it with `T` or `MyAwesomeType` and it would be exactly the same. – James McNellis Jul 30 '11 at 18:40
  • @James, oh, that's the thing that bugged me in my own answer, edited that now, the cast is also to `T*`, forgot about that :) thx – unkulunkulu Jul 30 '11 at 18:43