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 Envelope
s and triggering a stack overflow.
I'm not entirely sure what I'm doing wrong here.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With