Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Protecting copy constructor with std::enable_if

I've written a class to facilitate type erasure that has the following constructors:

class Envelope {
public:
    Envelope() {}

    template<typename Runnable>
    Envelope(Runnable runnable)
        : m_runFunc(&Envelope::RunAndDeleteRunnable<Runnable>), m_runnable(new Runnable(runnable)) {
    }

    template<typename Runnable>
    Envelope(Runnable * runnable)
        : m_runFunc(&Envelope::RunRunnable<Runnable>), m_runnable(runnable) {
    }
};

I want to rewrite the first non-default constructor to take a reference rather than a value (Runnable & runnable rather than Runnable runnable), but if I do that then copying with a non-const Envelope like so

Envelope next(...);
Envelope otherNext(next);

invokes that constructor rather than the copy constructor, and I get a stack overflow.

I think I can prevent that constructor from being called when Runnable == Envelope with std::enable_if like so

template<typename Runnable = typename std::enable_if<std::negate<std::is_same<Runnable, Nova::Envelope>>::value, Runnable>::type>
Envelope(Runnable & runnable)
    : m_runFunc(&Envelope::RunAndDeleteRunnable<Runnable>), m_runnable(new Runnable(runnable)) {
}

and it compiles fine (although it triggers some intellisense errors in Visual Studio 2015, which is mildly annoying), but it doesn't stop that constructor from being called with non-const Envelopes and triggering a stack overflow.

I'm not entirely sure what I'm doing wrong here.

like image 473
nzerbzer Avatar asked Jan 04 '23 01:01

nzerbzer


1 Answers

The easiest way to prevent this is to add a "non const" copy constructor:

class Envelope {
public:
    Envelope() {}
    Envelope(const Envelope&) = default;
    Envelope(Envelope& e) : Envelope(const_cast<const Envelope&>(e)) {}
    ...
    }
};

You're not doing anything in particular wrong, it's just that when you write constructors that take one templated parameter (or a variadic number), they tend to be "sticky" and intercept things intended for the copy constructor. The copy constructor does not get any special treatment that I'm aware of when it comes to selecting which function gets called. The templated functions are simply a better match for a non-const object. By adding a concrete (non-template) that matches the non-const case, there will now be a tie in goodness-of-match between that function and the template. And in cases of a tie a function always beats out a template.

like image 71
Nir Friedman Avatar answered Jan 17 '23 19:01

Nir Friedman