1

In designing a DSL (which compiles into C++), I found it convenient to define a wrapper class that, uppon destruction, would call a .free() method on the contained class:

template<class T> 
class freeOnDestroy : public T {
    using T::T;
public:
    operator T&() const { return *this; }
    ~freeOnDestroy() { T::free(); }
};

The wrapper is designed to be completely transparent: All methods, overloads and constructors are inherited from T (at least to my knowledge), but when included in the wrapper, the free() method is called uppon destruction. Note that I explicitly avoid using T's destructor for this since T::free() and ~T() may have different semantics!

All this works fine, untill a wrapped class gets used as a member to a non-reference templated call, at which point freeOnDestroy is instantiated, calling free on the wrapped object. What I would like to happen is for the tempated method to use T instead of freeOnDestroy<T>, and to implicitly cast the parameter into the supperclass. The following code sample illustrates this problem:

// First class that has a free (and will be used in foo)
class C{
    int * arr;
public:
    C(int size){ 
        arr = new int[size]; 
        for (int i = 0; i < size; i++) arr[i] = i;
    }
    int operator[] (int idx) { return arr[idx]; }
    void free(){ cout << "free called!\n"; delete []arr; }
};

// Second class that has a free (and is also used in foo)
class V{
    int cval;
public:
    V(int cval) : cval(cval) {}
    int operator[] (int idx) { return cval; }
    void free(){}   
};

// Foo: in this case, accepts anything with operator[int]
// Foo cannot be assumed to be written as T &in!
// Foo in actuality may have many differently-templated parameters, not just one
template<typename T>
void foo(T in){
    for(int i = 0; i < 5; i++) cout << in[i] << ' ';
    cout << '\n';
}

int main(void){
    C c(15);
    V v(1);
    freeOnDestroy<C> f_c(15);
    foo(c); // OK!
    foo(v); // OK!
    foo<C>(f_c); // OK, but the base (C) of f_c may not be explicitly known at the call site, for example, if f_c is itself received as a template
    foo(f_c); // BAD: Creates a new freeOnDestroy<C> by implicit copy constructor, and uppon completion calls C::free, deleting arr! Would prefer it call foo<C>
    foo(f_c); // OH NO! Tries to print arr, but it has been deleted by previous call! Segmentation fault :(
    return 0;
}

A few non solutions I should mention are:

  • Making freeOnDestroy::freeOnDestroy(const freeOnDestroy &src) explicit and private, but this seems to override T's constructor. I'd hoped it would try to implicitly convert it to T and use that as the template argument.
  • Assume foo receives a reference of its templated arguments (as in void foo(T &in): This is neither the case, nor desirable in some cases
  • Always explicitly template the call to foo, as in foo<C>(f_c): f_c itself may be templated, so it's hard to know to instantiate foo with C (yes, this could be done with creating multiple versions of foo, to remove the wrappers one by one, but I can't find a way of doing that without creating a different overload for each templated argument of foo).

In summary, my question is: Is there a clean(ish) method to ensure a base class will be casted to its superclass when resolving a template? Or, if not, is there some way of using SFINAE, by causing a substitution failure when the template argument is an instance of the wrapper class, and thus force it to use the implicit cast to the wrapped class (without duplicating each foo-like method signature possibly dozens of times)?

I presently have a work-arround that involves changes in the DSL, but I'm not entirely happy with it, and was curious if it was at all possible to design a wrapper class that works as described.

André Harder
  • 354
  • 2
  • 11

3 Answers3

1

The problem here not when "wrapped class gets used as a member to a non-reference templated call".

The problem here is that the template wrapper -- and likely its superclass too -- has violated the Rule Of Three.

Passing an instance of the class as a non-reference parameter is just another way of saying "passing by value". Passing by value makes a copy of the instance of the class. Neither your template class -- nor its wrapped class, most likely -- has an explicit copy constructor; as such the copied instance of the class has no knowledge that it is a copy, hence the destructor does what it thinks it should do.

The correct solution here is not to hack something up that makes passing an instance of freeOnDestroy<T> by value end up copying T, rather than freeOnDestroy<T>. The correct solution is to add a proper copy-constructor and the assignment operator to both the freeOnDestroy template, and possibly any superclass that uses it, so that everything complies with the Rule Of Three.

Community
  • 1
  • 1
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Hadn't thought of that alternative, and yes, adding a `bool isCopy` and initializing/checking it appropriately to `freeOnDestroy` does solve the problem, though at the expense of an extra boolean and a branch for every time the called function is returned -- probably not terribly expensive (although I'm a little concerned the extra size could cause block missalignment and confuse the c++ compiler optimizer, which could then be VERY expensive). As far as good design goes though, this is a correct solution. – André Harder Jul 03 '16 at 00:16
0

You can use a properly defined detector and a sfinaed function, as it follows:

#include<iostream>
#include<type_traits>

template<class T> 
class freeOnDestroy : public T {
    using T::T;
public:
    operator T&() const { return *this; }
    ~freeOnDestroy() { T::free(); }
};

template<typename T>
struct FreeOnDestroyDetector: std::false_type { };

template<typename T>
struct FreeOnDestroyDetector<freeOnDestroy<T>>: std::true_type { };

class C{
    int * arr;
public:
    C(int size){ 
        arr = new int[size]; 
        for (int i = 0; i < size; i++) arr[i] = i;
    }
    int operator[] (int idx) { return arr[idx]; }
    void free(){ std::cout << "free called!\n"; delete []arr; }
};

class V{
    int cval;
public:
    V(int cval) : cval(cval) {}
    int operator[] (int idx) { return cval; }
    void free(){}   
};

template<typename..., typename T>
std::enable_if_t<not FreeOnDestroyDetector<std::decay_t<T>>::value>
foo(T in) {
    std::cout << "here you have not a freeOnDestroy based class" << std::endl;
}

template<typename..., typename T>
std::enable_if_t<FreeOnDestroyDetector<std::decay_t<T>>::value>
foo(T &in) {
    std::cout << "here you have a freeOnDestroy based class" << std::endl;
}

int main(void){
    C c(15);
    V v(1);
    freeOnDestroy<C> f_c(15);
    foo(c);
    foo(v);
    foo<C>(f_c);
    foo(f_c);
    foo(f_c);
    return 0;
}

As you can see by running the example, free is called only once, that is for the freeOnDestroy created in the main function.
If you want to forbid definitely freeOnDestroy as a parameter, you can use a single function as the following one:

template<typename..., typename T>
void foo(T &in) {
    static_assert(not FreeOnDestroyDetector<std::decay_t<T>>::value, "!");
    std::cout << "here you have a freeOnDestroy based class" << std::endl;
}

Note that I added a variadic parameter as a guard, so that one can no longer use foo<C>(f_c); to force a type to be used.
Remove it if you want to allow such an expression. It was not clear from the question.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • Forbidding any use of wrapped classes is not really desired, since the goal is for them to be used in function calls as if they are the wrapped class `T`, not to ban them entirely! As mentioned in the question, while making `in` a reference, such as `foo(T &in)`, does solve the immediate problem (even without explicitly detecting the wrapper!), it looses the pass-by-value semantics that may be required for `foo` to function properly. Note that, as expected, removing the `&` from your second definition of foo reverts the code to the previous behavior of calling `.free()` twice. – André Harder Jul 03 '16 at 14:46
  • @AndréHarder Well, yes, but in the example there are **two** *sfinae*d `foo` functions. The former gets its parameter by copy, as required, while the latter gets it by reference. Note that the latter is the one that detect if a `freeOnDestroy` wrapper already exists. – skypjack Jul 03 '16 at 14:49
  • True enough, and it's definitely better than using a single `foo(T &in)`! We still, however, loose pass by value semantics for when we have the wrapper. – André Harder Jul 03 '16 at 15:24
  • @AndréHarder You can remove the `&` and decide what to do there. It goes without saying that if you copy the wrapper you delete the data twice. As an example, you could _disable_ the call to free whenever you detect that a wrapper as been used as a parameter, as an example. It mostly depends on the software you are working on, I cannot say. – skypjack Jul 03 '16 at 15:30
0

One solution, which, although a little ugly, seems to work, is to use an overloaded unwrapping method, such as:

template<typename T> T freeOnDestroyUnwrapper(const T &in){ return in; }
template<typename T> T freeOnDestroyUnwrapper(const freeOnDestroy<T> &in){ return in; }
template<typename T> T freeOnDestroyUnwrapper(const freeOnDestroy<typename std::decay<T>::type> &in){ return in; }
template<typename T> T& freeOnDestroyUnwrapper(T &in){ return in; }
template<typename T> T& freeOnDestroyUnwrapper(freeOnDestroy<T> &in){ return in; }
template<typename T> T& freeOnDestroyUnwrapper(freeOnDestroy<typename std::decay<T>::type> &in){ return in; }

Then, calls can be made using the unwrapper:

int main(void){
    C c(15);
    V v(1);
    freeOnDestroy<C> f_c(15);
    foo(freeOnDestroyUnwrapper(c));
    foo(freeOnDestroyUnwrapper(v));
    foo<C>(freeOnDestroyUnwrapper(f_c));
    foo(freeOnDestroyUnwrapper(f_c));
    foo(freeOnDestroyUnwrapper(f_c));
    return 0;
}

Or, to make this less verbose, we can alter foo so it does this for us:

template<typename T>
void _foo(T in){
    for(int i = 0; i < 5; i++) cout << in[i] << ' ';
    cout << '\n';
}
template<typename... Ts>
void foo(Ts&&... args){
    _foo(freeOnDestroyUnwrapper(args)...);
}

And then call it as normal:

int main(void){
    C c(15);
    V v(1);
    freeOnDestroy<C> f_c(15);
    foo(c);
    foo(v);
    //foo<C>(f_c); // This now doesn't work!
    foo(f_c);
    foo(f_c);
    return 0;
}

This seems to work for any number of arguments foo may have (of different templates, if needed), and seems to behave appropriately when foos input is a reference (which does not occur in my context, but would be good for the sake of making this solution generic).

I'm not convinced that this is the best solution, or that it generalizes to every case, plus, having to double all declarations is a bit cumbersome, and opaque to most IDEs autocomplete features. Better solutions and improvements are welcome!

André Harder
  • 354
  • 2
  • 11