Compile Time Checking of Class Template Types

But all I wanted to do was…

Oh, how many times have we all said that? This adventure started as I was coding ChaosExplorer. I had completed the coding for displaying Mandelbrot and Multibrot Sets and their associated Julia Sets. I have two classes derived from wxGLCanvas, called MultibrotPanel and MandelJuliaPanel. The first panel displays the Multibrot Sets and the second panel displays the corresponding Julia Sets. In the MultibrotPanel class, I have methods that are used to animate a number of properties. These methods are bound to timer events. I am not going to go into detail here; you can see the code on GitHub.

Here is the gist of it. I have a StartTimer method and a StopandReleaseTimer method that are defined as follows:

   void StartTimer(const int timerInterval, TimerHandler handler);
    void StopAndReleaseTimer(TimerHandler handler);

TimerHandler is defined using the following alias:

using TimerHandler = void(MultibrotPanel::*) (wxTimerEvent&);

Great! Everything works. Now I wanted to use other formulae to display other fractals and their Julia Sets. Much of the code in MultibrotPanel and MandelJuliaPanel would be duplicated, with the major differences being the use of different GLSL shader programs. The fractal panels similar to MultibrotPanel also reference different Julia Set panels because the Julia Set displays are created as the result of selecting menu items in the context menus in the fractal panels. So this suggested that the common functionality of the various panels should be placed in base classes and the code specific to each fractal formula should be in derived classes. Also, because the various panels reference different shader programs and Julia Set panels, templatizing the panel classes would be a good idea. The diagram, below, shows the class hierarchy.

panels.png

My first attempt resulted in the following declarations:

template <typename T, typename U>
class MultibrotPanel<T, U> : public PlottingCPanel<T, U>
{...};
template <typename T, typename U>
class PlottingCPanel<T, U> : public ChaosPanel<T>
{...};
template <typename T>
class ChaosPanel<T> : public wxGLCanvas
{...};

where T is the shader program class that is used to draw the fractal, and U is the panel class that displays the corresponding Julia Sets.

Compiling this produces an error on the TimerHandler alias definition that is shown above. I could not figure out how to proceed, so I jumped over to Stack Overflow to post the problem. Arunmu posted a possible solution that involved checking that the TimerHandler pointed to a class that contained an appropriate callback for the timer. A bit of an ugly solution, but the problem at least is caught at compile time.

While studying the solution, I realized that there is only one shader program and one Julia Set panel that the MultibrotPanel class references. Therefore, MultibrotPanel does not need to be a templated class at all; only PlottingCPanel (and ChaosPanel) need to be templated classes. So, the MultibrotPanel class declaration becomes:

class MultibrotPanel : public PlottingCPanel<GLMultibrotShaderProgram, MandelJuliaPanel> {...};

and the TimerHandler alias can stay in its original form. So, my initial problem is solved! But that got me thinking. I know, that is a dangerous thing. The first templated class in PlottingCPanel must be a shader program, and the second templated class must be a Julia Set panel. But as declared, there is nothing to prevent the programmer from using any class in either position. It would therefore be nice to ensure, at compile time, that the declared classes are the correct type. And this brings us back to the answer to my post on Stack Overflow and a number of other posts as well. The posts use a combination of constexpr and static_assert to ensure at compile time that the classes are of the right type.

The first class in the PlottingCPanel declaration is a shader program class. All shader program classes derive from the GLShaderProgram class. This class is not templated, so checking is quite easy:

#include <type_traits>
...
        static_assert(std::is_base_of<GLShaderProgram, T>::value, "T must be derived from GLShaderProgram");

The static_assert is the first statement in the ChaosPanel constructor. The second class in the PlottingCPanel class declaration must be a PlottingZ0Panel which is templated, so the simple solution used for checking the shader program class will not work.

How do we check that a templated class is the correct type? This brings us back to Arunmu’s answer to my Stack Overflow post and to other posts on Stack Overflow and a few other sites as well. I have created a simple program that tests these solutions. Here is the framework for the program. C is a templated class; the class specified by the template must be derived from A. All other classes should generate a compile error.

template<typename T>
class A
{
public:
// note that func only needs to be declared and not defined for test2 and test3 // static_asserts to work
void func();
};

class B : public A<int>
{};

class D {};

template<typename T>
class C
{
public:
C()
{
}
};

int main(int argsCount, char** args)
{
C<B> c;    // OK. Should compile
C<D> d;    // Should not compile because D is not derived from A
}

What do we need to add to generate the compile error for C<D> d;? In the Stack Overflow post How to Determine if a Type is Derived from a Template Class, Johannes Schaub – litb recommended a solution like the following:

template< typename T, typename U >
std::true_type is_based_impl(A const volatile&);
std::false_type is_based_impl(...);

template< typename T >
bool is_based_in_A(T&& t) {
    return decltype(is_based_impl(t))::value;
}

This solution uses templated variables which are not supported in Visual Studio 2015.

Solutions That Work

All of the solutions below use SFINAE (Substitution Failure Is Not An Error). The following statements comes from the SFINAE web page at cppreference.com. See that page for more information.

This rule applies during overload resolution of function templates: When substituting the deduced type for the template parameter fails, the specialization is discarded from the overload set instead of causing a compile error.

is_base_of_template

I have placed each potential solution inside a different namespace so that the solutions are easily separated. From the post is_base_of of generic type, the following solution is proposed:

namespace test1 {
    // From http://coliru.stacked-crooked.com/a/eaeac2b9008a97d9 in answer to 'is_base_of of generic type'
    // at http://stackoverflow.com/questions/32477691/is-base-of-of-generic-type/32489730#32489730
    template <template <typename...> class Base, typename Derived>
    struct is_base_of_template
    {
        using U = typename std::remove_cv<Derived>::type;

        template <typename... Args>
        static std::true_type test(Base<Args...>*);

        static std::false_type test(void*);

        using type = decltype(test(std::declval<U*>()));
    };

    template <template <typename...> class Base, typename Derived>
    using is_base_of_template_t = typename is_base_of_template<Base, Derived>::type;
}

To use this, simply add

static_assert(test1::is_base_of_template_t<A, T>::value, "No!");

to the C class constructor. Compiling the program using Visual Studio 2015 generates the following output:

main.cpp
1>...\main.cpp(88): error C2338: No!
1>  ...\main.cpp(86): note: while compiling class template member function 'C<D>::C(void)'
1>  ...\main.cpp(97): note: see reference to function template instantiation 'C<D>::C(void)' being compiled
1>  ...\main.cpp(97): note: see reference to class template instantiation 'C<D>' being compiled

Test For Method – 1

For this solution, the templated base class must have a method that is unique to that class declared; it is not necessary to provide a definition for the method if the only purpose for this unique method is to test that the class is being used. Here is the code:

namespace test2 {
    // Based on Arunmu's answer to a somewhat related question that I asked on Stack Overflow:
    // http://stackoverflow.com/questions/37509611/templating-classes-and-aliases-causes-compile-errors/37510350#37510350
    template <typename T>
    constexpr auto has_func(const T& obj) -> decltype(std::declval<T&>().func(), bool())
    {
        return true;
    }

    constexpr auto has_func(...)
    {
        return false;
    }
}

To use this code, add

static_assert(test2::has_func(T()), "Another No!");

to the C class constructor. When compiled with Visual Studio, the following error messages are generated:

1>...\main.cpp(89): error C2338: Another No!
1>  ...\main.cpp(86): note: while compiling class template member function 'C<D>::C(void)'
1>  ...\main.cpp(97): note: see reference to function template instantiation 'C<D>::C(void)' being compiled
1>  ...\main.cpp(97): note: see reference to class template instantiation 'C<D>' being compiled

Test For Method – 2

Here is a second technique for testing for a unique method in the base class. In it, sizeof is used to distinguish between a class that has the method, and a class that does not.

namespace test3 {
    // based on one of guestgulkan's answers to 'writing and using a C++ template to check for a function's existence'
    // at http://www.cplusplus.com/beginner/70134/
    template <typename T>
    class Has_Func
    {
        typedef char one;
        typedef long two;

        template <typename C>
        static const one test(decltype(&C::has_func));
        template <typename C>
        static const two test(...)
        {
            return 962;         // could be any long int value
        }

    public:
        template <typename U>
        static constexpr bool has_func()
        {
            return (sizeof(test(U)) == sizeof(char));
        }

        static constexpr bool has_func()
        {
            return false;
        }
    };
}

Adding the following line to the C class constructor

static_assert(test3::Has_Func::has_func(), "Does not have func()");

and compiling produces the following compile error messages:

1>...\main.cpp(90): error C2338: Does not have func()
1>  ...\main.cpp(86): note: while compiling class template member function 'C<D>::C(void)'
1>  ...\main.cpp(97): note: see reference to function template instantiation 'C<D>::C(void)' being compiled
1>  ...\main.cpp(97): note: see reference to class template instantiation 'C<D>' being compiled

Using All Three Techniques

The test program uses all three techniques. Having all three static_assert calls in the constructor yields the following compiler output:

main.cpp
1>...\main.cpp(88): error C2338: No!
1>  ...\main.cpp(86): note: while compiling class template member function 'C<D>::C(void)'
1>  ...\main.cpp(97): note: see reference to function template instantiation 'C<D>::C(void)' being compiled
1>  ...\main.cpp(97): note: see reference to class template instantiation 'C<D>' being compiled
1>...\main.cpp(89): error C2338: Another No!
1>...\main.cpp(90): error C2338: Does not have func()

Note that lines 3-5 are included only once.

Limitations

  1. These techniques work only for public base classes.
  2. The second and third techniques work only for public methods.

Analysis

  1. The is_base_of type trait only works with non-templated base classes.
  2. The second and third techniques require that a method with a name that is unique to the base class be declared in the base class.
  3. It is not necessary to provide a definition for this method if its only purpose is to check for use of the base class.
  4. The first technique is the cleanest because it does not require any additions to the base class.

Conclusions

  1. It is possible to check at compile time if a template class is derived from a templated base class.
  2. This check should be performed at compile time because using a non-templated base class as a template class when a templated base class should be used is a design/code error, and therefore should be caught by the developer and not shown to the end user.
  3. The first technique is the cleanest and therefore the preferred technique for determining if a template class is derived from a templated base class.

The source code for the program is available on GitHub.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s