Sam Liddicott
Sam Liddicott

Reputation: 1386

How to fix error refactoring decltype inside template

edit Possibly can't be done, see Clean implementation of function template taking function pointer although answer 1 there has a C macro work-around https://stackoverflow.com/a/18706623/2332068


I'm passing a function into a template to become a pre-supplied argument to the constructor, but also need to use decltype on that function to pass the function type to unique_ptr<...> template instantiator(? is that the right word)

It works if I pre-use decltype as an extra template argument, but not if I invoke it inside the template on the function passed as a parameter.

I'm using g++ 4.9.2, and extending my explorations here Calling inherited template constructor of unique_ptr subclass where I subclass unique_ptr to have a fixed destructor, I find that some destructor functions do not return void, so I want a more generic template that does not need to specify the destructor function type.

My current code is:

void free_int(int* p) {
  delete p;
}

template<typename T, void (*D)(T*)>
class unique_dptr : public std::unique_ptr<T, decltype(D)> {
    public: unique_dptr(T* t) : std::unique_ptr<T, decltype(D)>(t, D) { };
};

using int_ptr = unique_dptr<int, ::free_int>;
int_ptr i(new int(2));

but note the void (*D)(T*) calling signature to restrict the destructor to a void function that takes a pointer to T

Given normal use of unique_ptr in this form:

unique_ptr<foo, decltype(&::free_foo)>

I want to have something like this:

template<typename T, typename D>
class unique_gptr : public std::unique_ptr<T, decltype(&D)> {
    public: unique_gptr(T* t) : std::unique_ptr<T, decltype(&D)>(t, D) { };
};

using int_gptr = unique_gptr<int, ::free_int>;
int_gptr ig(new int(2));

but the compiler hates it:

error: template argument 2 is invalid
class unique_gptr : public std::unique_ptr<T, decltype(&D)> {
                                                          ^

No doubt ancient C-macro style token pasting is what I am wrongly aiming at.

I have tried removing the & from decltype(&D) but that leaves the error:

error: argument to decltype must be an expression

however this is OK:

template<typename T, typename D, D F>
class unique_gptr : public std::unique_ptr<T, D> {
    public: unique_gptr(T* t) : std::unique_ptr<T, D>(t, F) { };
};

using int_gptr = unique_gptr<int, decltype(&::free_int), ::free_int>;
int_gptr ig(new int(2));

but I wonder what I am doing wrong that I can't manage move the decltype(&::free_int) into the template.


Other solutions

I realise that I can just write additional specialisations for other fixed free-function types, replacing void(*)(void*)

I realise that I can override the std::default_delete for my type.

But this is really an exercise in template composition

Upvotes: 4

Views: 2116

Answers (3)

majie
majie

Reputation: 1529

I think for c++11 it is impossible to achieve what you want.

template<typename T, typename D>
class unique_gptr : public std::unique_ptr<T, decltype(&D)> {
    public: unique_gptr(T* t) : std::unique_ptr<T, decltype(&D)>(t, D) { };
};

using int_gptr = unique_gptr<int, ::free_int>;
int_gptr ig(new int(2));

Notice that decltype is applied on D, which is declared as a typename. So D is a type. But decltype can't be used on a type.

Firstly the code tries to get the address of a type (&). Secondly, the argument of decltype is expected to be an expression, but not a type or "the address of a type". To make it easier to understand, we can say that decltype expects its argument to be a “variable”, like the following example.

int main()
{
    int i = 10;
    decltype(i) j;
    decltype(int) k; /* Compiler error. */
    decltype(&int) l; /* Compiler error. */
    return 0;
}

You also want the compiler to replace D with ::free_int. And ::free_int is passed in as a non-type template argument. However D is a type template parameter. A non-type template parameter starts with an actual type (e.g. int, struct a* or a type template name). While a type template parameter starts with typename or class. An easier example for non-type template parameter,

template<int INIT>
void func2(void)
{
    decltype(INIT) j = INIT;
}

int main()
{
    func2<10>();
    return 0;
}

When you pass in a function pointer like ::free_int, you need a non-type template parameter, which must be preceded by a type. And you want the type of the function pointer to be "replaceable". So the type of the function pointer has to be a type template parameter. These make them two template parameters.

That's the reason you need three template parameters in template<typename T, typename D, D F>, which is the best result you have.

Upvotes: 2

orlp
orlp

Reputation: 117641

You can use some macro magic:

template<class T, class D, D d>
struct UniqueDeleter : public std::unique_ptr<T, D> {
    UniqueDeleter(T* t) : std::unique_ptr<T, D>(t, d) { };
};

template<class R, class T>
T decl_unique_deleter_ptr(R (*d)(T*));

#define DECL_UNIQUE_DELETER1(f) UniqueDeleter<decltype(decl_unique_deleter_ptr(&f)), decltype(&f), &f>
#define DECL_UNIQUE_DELETER2(T, f) UniqueDeleter<T, decltype(&f), &f>
#define DECL_UNIQUE_DELETER_GET_MACRO(_1, _2, NAME, ...) NAME
#define DECL_UNIQUE_DELETER_EXPAND(x) x
#define decl_unique_deleter(...) DECL_UNIQUE_DELETER_EXPAND(DECL_UNIQUE_DELETER_GET_MACRO( \
    __VA_ARGS__, DECL_UNIQUE_DELETER2, DECL_UNIQUE_DELETER1, _)(__VA_ARGS__))

Now you can use it like such:

decl_unique_deleter(int, free_int) ig(new int(2));

Or with using and some more magic:

using int_gptr = decl_unique_deleter(free_int); // If accepted pointer type matches.
int_gptr ig(new int(2));

I might also recommend an alternative solution:

auto i = new int(2);
BOOST_SCOPE_EXIT_ALL(&) { delete i; };

Upvotes: 1

Johan
Johan

Reputation: 3778

You cannot use decltype with a type as the goal is to obtain the type of a variable. But in decltype(&D), D is a type.

I would rather pass throught a template function:

template<typename T, typename D, D F>
class unique_gptr : public std::unique_ptr<T, D> {
    public: unique_gptr(T* t) : std::unique_ptr<T, D>(t, F) { };
};

template <typename T, typename D>
unique_gptr<T, D F> make_unique_gptr(T pointer, D deleter)
{
    return unique_gptr<T, D, deleter>(pointer);
}

auto ptr = make_unique(new int(2), ::free_int);

Upvotes: 2

Related Questions