4 ms·
I like it! The self-documentation of where the call is coming-from is quite nice (though a more descriptive name like CallFrom<T> would make that even clearer,
by mwkaufma 7y ago
I like it! The self-documentation of where the call is coming-from is quite nice (though a more descriptive name like CallFrom<T> would make that even clearer, were it the principle intent). I can't help myself, though:
template<typename T>
Badge<T> FakeBadge() {
struct Stub {}
return reinterpret_cast<Badge<T>>(Stub());
}
- kazinator 7y agoDoesn't subvert Device badges for me. Complete sample: template<typename T> class Badge { friend T; Badge() {} }; template<typename T> Badge<T> FakeBadge() { struct Stub {}; return reinterpret_cast<Badge<T>>(Stub()); }; class Device { public: static Badge<Device> getBadge() { return Badge<Device>(); } }; void needDeviceBadge(Badge<Device> badge) { } int main() { needDeviceBadge(Device::getBadge()); // no error needDeviceBadge(FakeBadge<Device>()); // error! return 0; } g++ errors: badge.cc: In instantiation of ‘Badge<T> FakeBadge() [with T = Device]’: badge.cc:25:38: required from here badge.cc:10:10: error: invalid cast from type ‘FakeBadge() [with T = Device]::Stub’ to type ‘Badge<Device>’ return reinterpret_cast<Badge<T>>(Stub()); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A reinterpret_cast won't turn an instance of the Stub local class into a Badge<Device>. That doesn't even have to do with friendship/protection. If we move that line into the Device class, the reinterpret_cast still doesn't compile.
- a1369209993 7y agoIt does, however, work fine if you use a proper "* (Badge<T>* )&stub" cast.
- GrayShade 7y agoIsn't casting unrelated types undefined behavior?
- brianush1 7y agoYes, in the standard it isn't defined, but on most--or maybe even all?--systems and compilers it's perfectly defined, since there is no reason why a compiler would layout two different empty structs in different ways; they're both empty anyways. Edit: Actually, from reading some of the other comments in the thread, the standard apparently specifies that empty structs are 1 byte in size, so technically they would both be the same size, so casting between them should be fine.
- kazinator 7y agoIt's not defined on systems unless a document spells it out. However, the actual behavior can be deduced to be harmless from black-box testing. compiler source code and generate object code.
- kazinator 7y agoProblem is that it doesn't require a diagnostic, and the actual behavior of casting one empty struct foo {} to another empty struct bar {} is likely harmless and portable.
- kazinator 7y agoWell, that's a problem in the C++ type system, isn't it; because it (effectively) constructs a value of type Badge<Device>, even though it's not a piece of code sitting in the Device class scope. The rule should be that if the default constructor of T is not accessible in the scope, then any cast to T * or T & requires a diagnostic. Or something like that. If I can allocate a suitably aligned buffer of sizeof (T) zeros, and then treat that as a T, I've effectively constructed a T.
- a1369209993 7y agoNo, that's a good feature of the C++ type system, because it (effectively) allows the programmer to construct a value of type Badge<Device>, even if Device thinks it's okay to prevent that. It would be better to do eg "return Device::private:get_badge()", but C++ defectively doesn't support that.
- mwkaufma 7y agoInteresting, I'd always assumed reinterpret_cast<> was YOLO-anything-goes. You learn something every day ;)
- microtherion 7y agoThat's not a legal C++ cast, but it's conceivable that it could be made to work by casting to a reference type instead.
- vnorilo 7y agoBadge could prevent this by making the copy and move constructors private.
- a1369209993 7y agoYou actually need: template<typename T> Badge<T> FakeBadge() { static struct Stub {} stub; return *(Badge<T>*)&stub; };
- deleted 7y ago[deleted]