1

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.

Øyvind Roth
  • 217
  • 1
  • 2
  • 11
  • Assuming you can achieve this (which I doubt), but since `doSomething()` is virtual, how can you prevent it from being called via a pointer of type `Super*` to an object of type `SubWithTemplate<2>`? That call is only detectable at runtime! – A.S.H Jan 09 '17 at 00:48
  • I think that problem is solved by removing virtual and making the method protected. But still, a SubWithTemplate<2> instance may call its protected super method. In my case, that's acceptable because, the Super is not intended for inheritance by the library user, only the SubWithTemplate is exposed. – Øyvind Roth Jan 09 '17 at 16:03

1 Answers1

3

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()
 {
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • Consider changing the condition in the enable_if to `I!=2 && I==SUBNUMBER` to protect the function from overriding the `I` value. – Guillaume Racicot Jan 09 '17 at 06:13
  • Thank to all of you for this solution. It works fine for me, my main requirements are satisfied. But if some of you find a more beautiful recipe, you are very welcome. @max66, you write "...Not a great solution...", what are you thinking of, more specificly? – Øyvind Roth Jan 09 '17 at 16:08
  • @ØyvindRoth - Well... I think that it's not a great solution mainly because it's valid only from C++11 (your question isn't tagged C++11 but only C++, so I suppose that you asked for a C++98 solution) but also because working with SFINAE (although I love it) it's a little complex and it's really easy do the wrong thing (see, by example, the comment form Guillaume Racicot; answer updated accordingly) – max66 Jan 09 '17 at 22:05
  • I am using VS2013 which, as far as I understand, has an uncomplete C++11. It does not support SFINAE, but it supports the solution you suggested. – Øyvind Roth Jan 09 '17 at 23:26
  • By the way, how could I prevent the SubWithTemplate class from being instantiated unless the SUBNUMBER is within a given interval? – Øyvind Roth Jan 09 '17 at 23:29
  • 1
    @ØyvindRoth - SFINAE is a rule of C++ that precede C++11. My use of `std::enable_if` is a tipical SFINAE application (but a C++11 one). ps: answer improved (again) to try to respond to the last question. I have another idea but no time to verify it in this moment (maybe tomorrow). – max66 Jan 10 '17 at 00:28
  • Found out: static_assert(((SUBNUMBER >= 0) && (SUBNUMBER <= 20)), "class SubWithTemplate can only be instantiated with SUBNUMBER between 0 and 20"); on top in the SubWithTemplate declaration. Is that the preferred way? – Øyvind Roth Jan 10 '17 at 00:33
  • @ØyvindRoth - answer improved (a new method to prevent the `SubWithTemplate` class from being instantiated unless the `SUBNUMBER` is within a given interval that works in C++98 too). – max66 Jan 10 '17 at 18:19
  • @ØyvindRoth VS2013 supports SFINAE, it just doesn't support [_expression_ SFINAE](http://stackoverflow.com/a/12654277/5386374). [This](http://ideone.com/TbhyQp) worked as far back as MSVC 2010, for example, thanks to its C++0x support including (most of) ``. It wouldn't have been able to detect the existence of a specific member function, though, since that needs expression SFINAE (as far as I'm aware). – Justin Time - Reinstate Monica Jan 10 '17 at 19:42