Anteru's blog
  • Consulting
  • Research
    • Assisted environment probe placement
    • Assisted texture assignment
    • Edge-Friend: Fast and Deterministic Catmull-Clark Subdivision Surfaces
    • Error Metrics for Smart Image Refinement
    • High-Quality Shadows for Streaming Terrain Rendering
    • Hybrid Sample-based Surface Rendering
    • Interactive rendering of Giga-Particle Fluid Simulations
    • Quantitative Analysis of Voxel Raytracing Acceleration Structures
    • Real-time Hybrid Hair Rendering
    • Real-Time Procedural Generation with GPU Work Graphs
    • Scalable rendering for very large meshes
    • Spatiotemporal Variance-Guided Filtering for Motion Blur
    • Subpixel Reconstruction Antialiasing
    • Tiled light trees
    • Towards Practical Meshlet Compression
  • About
  • Archive

C++ tricks, #1: Compile time checks

February 10, 2007
  • Programming
approximately 7 minutes to read

While writing a library, you have sometimes to prevent users from making mistakes. To accomplish this, you have two choices: Either include run-time checking and throw an appropriate exception or try to catch as much as possible during compile time. Of course, the latter should be prefered when possible as it adds no runtime costs and forces users to use your library properly. Read on for a small example for compile time checks.

Situation

Let’s assume we have rolled out our own RTTI system and now we want to create a safe casting function. We only support single-inheritance to keep things simple. It should allow downcasting from a type to a further derived type, but not accross types. This means, if you have a class Main and two derived types, Left and Right, it should be possible to cast from Main to Left or Right but not from Left to Right (at least, not directly).

What can we check?

Well, let’s start what we cannot check. We have no way at compile time to see whether a Main pointer points at runtime to a derived object or not, so we have to do this at runtime. What we can check though is if someone tries to assign a Left object to a Right pointer. How do we do that? We have to check if the target is derived from the source or the other way round - if so, one class is derived from the other. Moreover, we have to check if both are the same - this is always safe. Instead of implementing own is_base_of checks, we’ll use the excellent boost/type_traits library. Here’s the code of our checker:

#include <boost/type_traits/is_same.hpp>
#include <boost/type_traits/is_base_of.hpp>

struct Assign
{
    template <bool CastIsValid> struct AssignImplementation;

    template <> struct AssignImplementation <true>
    {
        template <class Left, class Right>
        static void assign (Left* l, Right* r)
        {
            l = static_cast<Left*> (r);    // Your runtime cast here
        }
    };

    template <class Left, class Right>
    static void assign (Left* l, Right* r)
    {
        AssignImplementation
            <
                boost::is_same<Left, Right>::value        ||
                boost::is_base_of<Left, Right>::value    ||
                boost::is_base_of<Right, Left>::value
            >::assign (l, r);
    }
};

template <class Target, class Source>
void safe_assign (Target* target, Source* source)
{
    Assign::assign (target, source);
}

We check at compile time if the types can be casted, if so, we pass on to a partially specialized template (AssignImplementation) which should do the run-time check. In this example, I just used static_cast, for the sake of simplicity. If the compile-time check fails, AssignImplementation<false> will be called, and as it does not exist it will result in a compile-time error like

error C2027: use of undefined type "Assign::AssignImplementation<CastIsValid>"
        with
        [
            CastIsValid=false
        ]

Example

Some example code so you can try for yourself - should work with any recent standards-conforming C++ compiler.

class Main
{
};

class Left : public Main
{
};

class Right : public Main
{
};

class LeftDerived : public Left
{
};

int main (int argc, char* argv[])
{
    Left* leftDerived = new LeftDerived ();
    LeftDerived* leftDerivedPointer = 0;
    Right* right = 0;

    safe_assign (leftDerivedPointer, leftDerived);

    safe_assign (right, leftDerived); // Error!
}
Previous post
Next post

Recent posts

  • Data formats: Why CSV and JSON aren't the best
    Posted on 2024-12-29
  • Replacing cron with systemd-timers
    Posted on 2024-04-21
  • Open Source Maintenance
    Posted on 2024-04-02
  • Angular, Caddy, Gunicorn and Django
    Posted on 2023-10-21
  • Effective meetings
    Posted on 2022-09-12
  • Older posts

Find me on the web

  • GitHub
  • GPU database
  • Projects

Follow me

Anteru NIV_Anteru
Contents © 2005-2025
Anteru
Imprint/Impressum
Privacy policy/Datenschutz
Made with Liara
Last updated February 20, 2019