static assertion about inheritance

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • tanvalley
    New Member
    • Jun 2012
    • 17

    static assertion about inheritance

    Is this the canonical way to make a static assertion that class D is derived from class B?
    Code:
    (B)(*(D*)(0));
    Or is there any reason to use static_casts instead?
    Code:
    static_cast<B>(*static_cast<D*>(0));
    Neither seems to generate any code, even at no optimisation.
    Both give the error message "error: no matching function for call to 'B::B(D&)'" when the assertion fails, and are silent otherwise.
    Is there a way, easy or not (I intend to hide this in a macro), to produce a less cryptic error message? (Not that I really really care)
  • weaknessforcats
    Recognized Expert Expert
    • Mar 2007
    • 9214

    #2
    You can only static_cast pointers or references. It is your assertion that the target pointer or reference is, in fact, correct. The compiler will not verify your assertion beyond checking that the target type is in the inheritance hierarchy.

    Usually, a cast in C++ means a)you are maintaining legacy code that uses void* or b) your C++ design is flawed.

    A cast should never be part of the design. Instead, it is provided in C++ as a firefighting tool to be used only in an emergency. The syntax is deliberately ugly so the cast shows as a sore thumb in the code.

    As to whether D is derived from B, why would you care? If you are using polymorphism you only use a pointer or reference to the base class of the hierarchy and control behavior using virtual functions. A D object is accessed using a base class pointer or reference. Otherwise, you use a D object. A glance at the class declaration will reveal if D is derived from B.

    Also, here are C++ features to avoid macros: 1)inline functions, b) templates, c)function overloading. Other than using inclusion guards, I don't know why you would hide something in a macro.

    The problem with macros is that they are not part of the code so you can't debug through them. Instead you have you get a copy of the translation unit and figure out manually how the macro was expanded and what the compiler did with the expansion.

    Comment

    • tanvalley
      New Member
      • Jun 2012
      • 17

      #3
      If casts are so bad way are there hundreds of them (over 700 on my system) in the distributed standard template library?

      Comment

      • weaknessforcats
        Recognized Expert Expert
        • Mar 2007
        • 9214

        #4
        I don't know about your system. I do know that C programmers have a real hard time in C++ because the rules are different. In their code I see a lot of casts which is a substitute for design.

        The hard part about the casts is that the target type is hard-coded in the program. If that type goes away or a new type gets added, all the instances of the program need to be recompiled. Whereas with a base pointer and virtual functions you can add classes written today to code written years ago without ever recompiling that old code.

        As far as the STL goes, same discussion. I have read that the STL is poorly designed internally even tough it presents a uniform interface externally.

        C++ objects are to have their own behavior. Casting a B* to a D* does not change the object. If the interfaces are different on these two objects, you have just skrogged your program. In C++ programs the objects are in control and the programmer is not. A cast is the programmer grabbing control. Often if you remove the cast, the line of code won't even compile.

        Comment

        • tanvalley
          New Member
          • Jun 2012
          • 17

          #5
          I don't think I should have to justify my design to ask a simple question about static assertions, but here goes:

          I have a template class that a programmer (also me) uses directly in code:

          template<class T,class N> class R {
          ...
          }

          There's also a "service class" whose implementation is entirely hidden from the programmer:

          template<class N> class S;

          There's a requirement that T inherit from S<N> in order to provide some infrastructure needed by R.

          For example

          class N1;
          class N2;

          class Tx: S<N1>, S<N2> {
          ...
          }

          R<Tx,N1> r1;
          R<Tx,N2> r2;

          The user then invokes functions provided by R to perform actions on instances of Tx.

          If the user defines an instance of R (for example, R<Tx,N3>) where Tx does not inherit from S<N3> I want to produce a compile-time error message related directly to the point of definition, not later in the code where the user may or may not start invoking some use of the services of R that require the infrastructure provided by S. Because this is a template, the functions internal to the implementation of R are analysed only for syntax, not for semantics, until actually referenced in code.

          To accomplish this I want a static assertion in the version of the constructor instantiated for each particular R. There can be no runtime overhead for this, so inline functions are out of the question. Furthermore, as no generated code is allowed for this assertion, casting a null pointer to a S<N>* is not a runtime dereference; it's simply exploiting the strong typing provided, at compile time, by C++.

          In my example I want the definition

          R<Tx,N3> r3;

          to produce an error at compile time because Tx does not inherit from S<N3>. And the definitions of r1 and r2 above should not. Nor can the constructors for r1 or r2 incur any execution time penalty for this check.

          I'm not seeking advice about overall design. I am seeking advice about making static assertions in template instantiations.

          Comment

          • Banfa
            Recognized Expert Expert
            • Feb 2006
            • 9067

            #6
            Firstly

            static_cast<B>( *static_cast<D* >(0));

            does dereference the NULL pointer (which is not going to be pretty). You cast 0 to a pointer to type D then dereference it. Then you convert that object (the D located at 0) to a B.

            You can static_cast things other than pointers and references and it is not always a product of using legacy code, for example I often find myself casting integer types when, for example, you are using more than 1 3rd party framework and they have chosen to define an unsigned 32 bit value in different ways.

            Finally to assert that in

            template<class T,class N> class R {
            ...
            }

            T is a subclass of S<N> is really relatively simple, no cast required (and frankly better done without a cast.

            something equivalent of

            T* pt = 0;
            S<N>* ps = pt;

            will fail to compile if T does not subclass S<N> and you can easily put it into the constructor.

            However I believe weaknessforcats point is that if it is a requirement that T inherits S<N> then presumably there is an interface relationship between T and S<N>, i.e. T implements S<N>'s interface and in this case you template R might be better if it just dealt with the base class of the higherarchy, i.e. S<N>

            Code:
            template<class N> class R
            {
            public:
              R(S<N>& s) : _s(s) {}
            
              void function()
              {
                s.function();
              }
            
            private:
              S<N>& _s;
            }
            
            // and instantiated as
            
            class T : S<N1>
            {
            ...
            }
            
            T t;
            
            R<N1> r(t);
            The final line only compiles if T is a subclass of S<N1> and R accesses T through its base S<N1>.

            Comment

            • tanvalley
              New Member
              • Jun 2012
              • 17

              #7
              When the entire statement is
              static_cast<B>( *static_cast<D* >(0));
              there is no generated code and no runtime reference to location 0, so actually it is pretty.

              Furthermore, the sequence
              T* pt = 0;
              S<N>* ps = pt;
              when it does compile, as it should all of the time when the classes are used correctly, will usually produce executable code unless optimised away and possible warnings about unused variables. And in reality it's just the same as (B)(*(D*)(0)) except it's hiding casts behind assignment operators.

              Comment

              • Banfa
                Recognized Expert Expert
                • Feb 2006
                • 9067

                #8
                Compile and run this

                Code:
                class B
                {
                public:
                  B() {}
                  virtual ~B() {}
                
                private:
                  int bdata;
                };
                
                class D : public B
                {
                public:
                  D() {}
                  virtual ~D() {}
                  
                private:
                  int ddata;
                };
                
                int main()
                {
                  static_cast<B>(*static_cast<D*>(0));
                  
                  return 0;
                }
                On my system (WIN7 using mingw32) it crashes.

                On my system the executable size is reduced when line 23 is commented out because I assume there is less code.

                Like I said, your code dereferences the NULL pointer and that is inherently undefined behaviour.

                And that BTW is the difference between what you wrote and what I wrote, since what I wrote converts pointers (without casting) and what you wrote casts objects my code does not dereference the NULL pointer where yours does.

                Comment

                Working...