2

TL;DR

Is this casting/function call legal, standard-conforming C++11/14 code?
If not, would it be if there were no virtual functions (if std::is_standard_layout<Module> became true)?
(NB: It works on every compiler I've tested it so far...)

class Module
{
protected:
    virtual float protectedVirtualFunction(float f) { return f*f*f; }
    float protectedFunction(float f) { return f*f; }
};

class ModuleTester : public Module // consists of ONLY aliases
{
public:
    using Module::protectedFunction;
    using Module::protectedVirtualFunction;
};

int main()
{
    Module module;  //assume this is a pre-existing instance
    // Is this legal?
    ModuleTester* testerMask = static_cast<ModuleTester*>(&module); 
    testerMask->protectedFunction(4.4f);
    testerMask->protectedVirtualFunction(4.4f);
}

Additional Information

Usually, I aim to test a classes public API only when writing UnitTests. In some cases - like when you're dealing with legacy code you can't change - it's just more practical to access private members.

So, assuming we cannot change the design (DI, decoupling...), I see the following solutions:

  1. Make private members public (compromising all encapsulation)
  2. The ol' trick: #define private public in test context
  3. The "Tester" pattern (as used in example code)

The standard way of using this "Tester" pattern would be like, and that should be legal:

    ModuleTester tester;
    tester.protectedFunction(4.4f);
    tester.protectedVirtualFunction(4.4f);

However, sometimes I have an existing instance and it would be great if I could just apply this "tester mask" on top of it to gain access.

My guess is that as soon as I virtual functions and stop being "standard layout", there could be "undefined behaviour" strictly speaking. However, if I'm only defining aliases in the derived class, I don't see how this could go wrong as long as I use the same compiler for Module and ModuleTester.

EDIT: I found a similar method that doesn't use explicit pointer casting (https://stackoverflow.com/a/1725107/649700). It works, is arguably less readable and probably does not change the 'legality' status.

(module.*&ModuleTester::protectedVirtualFunction)(4.4f);

Other SO questions that make some good points: reinterpret_cast from object to first member, How do I unit test a protected method in C++?

Community
  • 1
  • 1
Sidelobe
  • 433
  • 7
  • 13
  • Another option would be to make the `class ModuleTester` a `friend` in `class Module` and (instead of inheriting) give it just a reference to `Module`. At least, you wouldn't need these scaring reinterprete casts. [**Demo on coliru**](http://coliru.stacked-crooked.com/a/11018d280a84875b). OK, that was stupid simple but stupid simple is not always bad. ;-) – Scheff's Cat Apr 03 '20 at 09:01
  • @Scheff "Stupid simple" is often the smartest thing to do ^^ And it allows you to boast with "I'm programming in accordance with the KISS principle!" – cmaster - reinstate monica Apr 03 '20 at 11:17
  • True @Scheff, I forgot to mention the friend option... it still requires me to update the production code for every test class I use, though, that's why I dislike that solution. That being said, it's definitely safe, but so is the "Tester Pattern" if I use it in the standard way (without casts) – Sidelobe Apr 03 '20 at 13:19

1 Answers1

1

This would be Undefined Behavior since &module does not point at subobject of object ModuleTester:

8.5.1.9 Static cast [expr.static.cast]
11 A prvalue of type “pointer to cv1 B ”, where B is a class type, can be converted to a prvalue of type “pointer to cv2 D ”, where D is a class derived (Clause 13) from B , if cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. If B is a virtual base class of D or a base class of a virtual base class of D , or if no valid standard conversion from “pointer to D ” to “pointer to B ” exists (7.11), the program is ill-formed. The null pointer value (7.11) is converted to the null pointer value of the destination type. If the prvalue of type “pointer to cv1 B ” points to a B that is actually a subobject of an object of type D , the resulting pointer points to the enclosing object of type D. Otherwise, the behavior is undefined.

#define private public is also not a good idea:

2 A translation unit shall not #define or #undef names lexically identical to keywords, to the identifiers listed in Table 4, or to the attribute-tokens described in 10.6.

user7860670
  • 35,849
  • 4
  • 58
  • 84
  • Yes, but couldn't one argue that `Module` and `ModuleTester` is technically the same object, since all that differentiates "parent and child" them are function aliases? – Sidelobe Apr 03 '20 at 13:17
  • 1
    @SideLobe There is only `Module` object, no instances of `ModuleTester` class are created. C++ does not allow simply treating one object as another even if they have the same layout. – user7860670 Apr 03 '20 at 13:42