Øyvind Roth
Øyvind Roth

Reputation: 255

How do I produce a compiler error upon an attempt to instantiate a template subclass method for certain non-type values?

I would like to generate a compiler error if the using program calls a non-type template class method for a certain template argument.

typedef SubWithTemplate<1> SubWithTemplate1;
typedef SubWithTemplate<2> SubWithTemplate2;

SubWithTemplate1 &subWithTemplate1 = SubWithTemplate1::instance;
SubWithTemplate2 &subWithTemplate2 = SubWithTemplate2::instance;

subWithTemplate1.doSomething(); // Should compile OK
subWithTemplate1.doSomethingElse(); // Should compile OK
subWithTemplate2.doSomething(); // Should NOT compile OK
subWithTemplate2.doSomethingElse(); // Should compile OK

My starting point is the two following classes:

Super.h:

class Super {
    protected:
        Super() {}
    public:
        virtual void doSomething();
        void doSomethingElse();
};

Super.cpp:

void Super::doSomething() {}
void Super::doSomethingElse() {}

SubWithTemplate.h:

template<int SUBNUMBER>
class SubWithTemplate : public Super {

    public:
        static SubWithTemplate<SUBNUMBER> instance;
        void doSomething() {
            // Do something
        };

    private:
        SubWithTemplate() : Super() {}
};

template<int SUBNUMBER>
SubWithTemplate<SUBNUMBER> SubWithTemplate<SUBNUMBER>::instance;

I am not very fluent in Boost or mpl, but I have some vague feeling that BOOST_MPL_ASSERT could bring me some success. But I am not capable of understanding the nitty-gritty.

I tried something like:

SubWithTemplate.h:

...
void doSomething() {
    BOOST_MPL_ASSERT_MSG(<some test on SUBNUMBER being different from 2 and 7 and less than 25>, <what here?>, <what here?> )
};
...

I do not want the Super to be templatized, as it should be the same instantiation for all subclasses.

If I could avoid the use of virtual on doSomething, even better.

I would be very thankful if some more-than-me-expert could help me.

Upvotes: 1

Views: 80

Answers (1)

max66
max66

Reputation: 66200

Not a great solution but... if you can use C++11, what about disabling doSomething() via SFINAE?

In the following example the doSomething() is enabled for all values of SUBNUMBER except 2

#include <type_traits>

class Super
 {
   protected:
      Super () {}
      void doSomething () {}

   public:
      void doSomethingElse () {}
 };

template <int SUBNUMBER>
class SubWithTemplate : public Super
 {
    public:
        static SubWithTemplate<SUBNUMBER> instance;

        template <int I = SUBNUMBER>
        typename std::enable_if<I!=2>::type doSomething ()
         { Super::doSomething(); }

    private:
        SubWithTemplate () : Super() {}
 };

template<int SUBNUMBER>
SubWithTemplate<SUBNUMBER> SubWithTemplate<SUBNUMBER>::instance;

typedef SubWithTemplate<1> SubWithTemplate1;
typedef SubWithTemplate<2> SubWithTemplate2;

int main()
 {
   SubWithTemplate1 &subWithTemplate1 = SubWithTemplate1::instance;
   SubWithTemplate2 &subWithTemplate2 = SubWithTemplate2::instance;

   subWithTemplate1.doSomething();     // OK
   subWithTemplate1.doSomethingElse(); // OK
   //subWithTemplate2.doSomething();   // compilation error
   subWithTemplate2.doSomethingElse(); // OK
 }

--- EDIT ---

As pointed by Guillaume Racicot (thanks!) this solution can be circumvented expliciting the template value (I = SUBNUMBER is only a default).

So if

subWithTemplate2.doSomething();

give a compilation error (as asked by the OP),

subWithTemplate2.doSomething<1>();

compile without problem.

To avoid this I can suggest a couple of solutions.

(1) you can add a static_assert(), in the body of the function, to impose that I == SUBNUMBER; something like

    template <int I = SUBNUMBER>
    typename std::enable_if<I!=2>::type doSomething ()
     {
       static_assert(I == SUBNUMBER, "I != SUBNUMBER; this in wrong");
       Super::doSomething();
     }

(2) as suggested by Guillaume Racicot (thanks again!), you can integrate I == SUBNUMBER in the std::enable_if<> test; something like

    template <int I = SUBNUMBER>
    typename std::enable_if<(I!=2) && (I == SUBNUMBER)>::type
       doSomething ()
     { Super::doSomething(); }

I find the second solution a little more elegant but I not an expert and, for me, it's a matter of taste.

--- EDIT 2 ---

how could I prevent the SubWithTemplate class from being instantiated unless the SUBNUMBER is within a given interval?

Hot to prevent the full class? Not only the doSomething() method?

The first way that come in my mind is the use of a static_alert().

By example, if you want permit only SUBNUMBERs in the range [5,10[ (5 included, 10 excluded), you can write the constructor as follows.

SubWithTemplate () : Super()
 { static_assert((SUBNUMBER >= 5) && (SUBNUMBER < 10), "error message"); }

But I suppose there are other ways.

--- EDIT 3 ---

Another way to prevent the SubWithTemplate class from being instantiated unless the SUBNUMBER is within a given interval.

A way that works in C++98 too.

It's based on default specialization and template default value.

class Super
 {
   protected:
      Super () {}
      void doSomething () {}

   public:
      void doSomethingElse () {}
 };

template<bool b> struct boolWrapper {};

template <int I, bool = (I >= 0) && (I <= 20)>
struct rangeLimit;

template <int I>
struct rangeLimit<I, true>
 { };

template <int SUBNUMBER>
class SubWithTemplate : public Super, public rangeLimit<SUBNUMBER>
 {
    public:
        static SubWithTemplate<SUBNUMBER> instance;

        void doSomething ()
         { Super::doSomething(); }

    private:
        SubWithTemplate () : Super() {}
 };

template<int SUBNUMBER>
SubWithTemplate<SUBNUMBER> SubWithTemplate<SUBNUMBER>::instance;

typedef SubWithTemplate<1> SubWithTemplate1;
typedef SubWithTemplate<2> SubWithTemplate2;
typedef SubWithTemplate<20> SubWithTemplate20;
//typedef SubWithTemplate<21> SubWithTemplate21; compilation error

int main()
 {
 }

Upvotes: 3

Related Questions