Reputation: 255
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
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 SUBNUMBER
s 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