c++ - Why isn't std::function working with perfect forwarding? -
note:
- i apologize in advance unable reduce/simplify code further (i.e., smaller tests couldn't reproduce issue)
- version 1 of code compiles does not execute properly using visual studio 2010 premium
- version 2 & 3 of code compiles , executes successfully using visual studio 2010 premium
- all versions of code compiles , executes successfully using ideone.com (c++14)
version 1 output (visual studio 2010)
signal1<a1>::raise(7) slot::operator()(7) slot1<a1>::operator()(7) handling new value: 3931764 // value changes on each execution
version 2 output (visual studio 2010)
signal1<a1>::raise(7) slot::operator()(7) slot1<a1>::operator()(7) handling new value: 7
version 3 output (visual studio 2010)
signal1<a1>::raise(7) slot::operator()(7) slot1<a1>::operator()(7) handling new value: 7
code
#include <functional> #include <iostream> #include <vector> // version 1: uses perfect forwarding std::function<void (t)> // version 2: uses perfect forwarding std::function<void (const t&)> // version 3: forgoes perfect forwarding std::function<void (t)> #define ver 1 class slot { public: template<typename a1> #if ver == 1 || ver == 2 void operator()(a1&& a1) const; #elif ver == 3 void operator()(a1 a1) const; #endif }; template<typename a1> class slot1 : public slot { public: template<typename t> slot1(t* instance, void (t::*fn)(a1)) : mfn(std::bind(fn, instance, std::placeholders::_1)) { // nothing } void operator()(a1 a1) const { std::cout << "slot1<a1>::operator()(" << a1 << ")\n"; mfn(a1); } private: #if ver == 1 || ver == 3 std::function<void (a1)> mfn; #elif ver == 2 std::function<void (const a1&)> mfn; #endif }; template<typename a1> #if ver == 1 || ver == 2 void slot::operator()(a1&& a1) const #elif ver == 3 void slot::operator()(a1 a1) const #endif { std::cout << "slot::operator()(" << a1 << ")\n"; #if ver == 1 || ver == 2 static_cast<const slot1<a1>&>(*this)(std::forward<a1>(a1)); #elif ver == 3 static_cast<const slot1<a1>&>(*this)(a1); #endif } class signal { public: void connect(slot* slot) { mslots.push_back(slot); } std::vector<slot*> mslots; }; template<typename a1> class signal1 : public signal { public: void raise(a1 a1) { std::cout << "signal1<a1>::raise(" << a1 << ")\n"; (*mslots[0])(a1); } }; class model { public: void setvalue(int value) { mvalue = value; mvaluechangedsignal.raise(value); } signal1<int> mvaluechangedsignal; private: int mvalue; }; class view { public: void handlechange(int value) { std::cout << "handling new value: " << value << "\n"; } }; int main() { view view; slot1<int> slot(&view, &view::handlechange); signal1<int> signal; signal.connect(&slot); signal.raise(7); return 0; }
is visual studio bug or doing wrong?
version 1 , 2 undefined behavior, slot::operator()
of slot1<int>
called a1
equal int&
within signal1<int>
, static_cast
s slot1<int&>
. not slot1<int&>
, cast generates bad reference, use, , boom, whatever happens happens.
please check types , stop explicit casting based of parameter types, ridiculously unsafe , annoying track down.
i uninterested in untangling mess of #ifdef
s in code determine if similar error occurs version 3. design fundamentally unsafe, relatively innocuous argument changes within argument passing slot
cause undefined behavior. should not implicitly taking deduced parameters operator()
, using cast type of this
derived type.
treat type casting respect.
here detailed breakdown of undefined behavior code causes in case 1:
signal.raise(7);
calls
signal1<int>::raise(int)
which calls
void raise(int a1) { (*mslots[0])(a1); }
here a1
lvalue of type int
. calls
slot::operator()(int& a1) const because how forwarding references work -- t&&
passed int&
deduces t
int&
. body contains
static_cast<const slot1<int&>&>(*this)(std::forward<int&>(a1));
which casts *this
reference slot1<int&>
, class unrelated object in question. undefined behavior results when try interact it.
as have said, fixing possible, fundamental problem deduction of type parameter in slot::operator()
not suitable way determine sub-type of slot
want cast *this
into. type of expression passed operator()
not, in general, obvious @ point of call, nor @ point of deduction. , if not type exactly right, result undefined behavior. can "seem work", until unrelated change occurs , breaks.
when casting to-derived from-base, must must must careful, explicit, , document doing @ every step.
if calls slot
(which slot1<int>
) unsigned int, undefined behavior. size_t, undefined behavior. unsigned char, undefined behavior. uint16_t, undefined behavior. adds long long unsigned int, calls result, undefined behavior. type implicitly converts int, undefined behavior.
polymorphism without type safety idea.
on top of this, there no reason in code this. signal1<a1>
implement connect
instead of signal
taking slot1<a1>
-- you, after all, have type right there -- , store array of slot1<a1>
instead of array of slot
. given the type of slot
not result in undefined behavior later on slot1<a1>
, why store wrong type @ all?
Comments
Post a Comment