Reputation: 1802
This is a simplified example of what I am trying to achieve. As such, it might look a bit silly but bear with me. Let's say I have
template<int i>
class Class1{
foo(){cout<<"j is divisible by i, so we will hang out"<<endl;}
}
and a class2
having a fixed int j
variable: either templatized by such an int or having a member variable. I want class2
instances to only be able to call foo()
if they satisfy a certain condition, in this case I want to ensure that say (j%i==0)
.
The best I can come up with it, is:
template<int i>
class Class1{
static const int intParam=i;
template<bool true>
foo(){cout<<"j is divisible by i, so we will hang out"<<endl;}
}
and then class 2 would call it like this:
foo<class1::intParam%j>()
which is not very nice. Is there a better way of doing this? I have seen 'std::enable_if' which is kind of relevant, but I am not too sure.
if you want the bigger picture, This is a signal/delegate brokering mechanism. In a system, Task objects should be able to be served/requested by performer objects only if they match the role enums(int i
) specified in the task. Essentially this is supposed to be enum-based design without dynamic polymorphism. Is there a better way of doing this in C++?
Upvotes: 1
Views: 1595
Reputation: 41780
As opposed to other solutions, I'd recommend using SFINAE to enable of disable functions depending on a condition.
template<int i>
struct Class1 {
template<int j, std::enable_if_t<i % j == 0>* = 0>
void foo() {
}
};
The advantage of this solution is that if there is other overloads for foo()
, the compiler will try them instead of giving a hard error. As you can see at this Live Example, the error given by the compiler is that:
main.cpp: In function 'int main()':
main.cpp:12:24: error: no matching function for call to 'Class1<3>::foo()'
Class1<3>{}.foo<2>();
^
This means that the error happens in the user's code, not in your header, and that if there is an alternative function when this one didn't worked, the compiler will try other overloads.
Upvotes: 3
Reputation: 41100
What you're really asking for is to make writing the code be an error, and I'm sad to say that's not really possible. I don't think there's an IDE out there that will literally prevent you from writing invalid code. However, if you implement one of my solutions below (the compile-time ones), then a sufficiently advanced IDE will be able to provide you with information that the code you wrote is bad before you hit compile.
A compiler like Visual Studio will essentially be "compiling" things for you in the background, and then underline bad code (read: won't compile) with red squiggly lines.
You suggest a couple of possibilities for your Class2
structure, so let's address each:
First we'll begin with your Class1
essentially as you defined it:
template<int i>
struct Class1{
// how to define a "foo" function that is only callable
// if Class2's j is evenly divisble by i?
};
Your first possibility is that Class2
has a templatized j
parameter:
template<int j>
struct Class2
{
//...
};
A good solution to this would be to template Class1
's foo
method, and then include a static_assert
, which is a compile-time assertion. This works because i
and j
are known at compile time.
Now Class1
looks like this:
template<int i>
struct Class1{
template<int j>
void foo()
{
static_assert(j%i==0, "j is not evenly divisible by i");
std::cout << "j is evenly divisble by i" << std::endl;
}
};
And Class2
can call foo
like this:
template<int j>
struct Class2
{
void CallFoo()
{
Class1<2> c1;
c1.foo<j>(); // works
//Class1<3> c2;
//c2.foo<2>(); // fails static assert
}
};
The other possibility you mention is that Class2
could have a member variable for j
. You can accomplish this so long as that member variable is constexpr
(and also static
as a result):
struct Class2
{
static constexpr int j = 4;
void CallFoo()
{
Class1<2> c1;
c1.foo<j>(); // works
//Class1<3> c2;
//c2.foo<2>(); // fails static assert
}
};
constexpr
defines a compile-time constant here. So the value is guaranteed to be known at compile-time and our static_assert
will work.
If j
is not constexpr
then we cannot achieve our compile time assertions. At that point you're relegated to run-time exception handling:
template<int i>
struct Class1{
void foo(int j)
{
if (j%i != 0)
throw std::invalid_argument("j is not evenly divisible by i");
std::cout << "j is evenly divisble by i" << std::endl;
}
};
struct Class2
{
int j = 4;
void CallFoo()
{
Class1<2> c1;
c1.foo(j); // works
j = 3;
c1.foo(j); // throws
}
};
Upvotes: 1
Reputation: 36483
Use static_assert
:
template<int i, int j>
class Class1
{
public:
void foo()
{
static_assert(i % j == 0, "Message upon i % j == 0 being false");
cout<<"j is divisible by i, so we will hang out"<<endl;
}
};
And call it e.g.
Class1<42, 24> f;
f.foo();
Update to comments, just add j
as an extra template parameter for foo()
then:
template<int i>
class Class1
{
public:
template<int j>
void foo()
{
static_assert(i % j == 0, "Message upon i % j == 0 being false");
cout<<"j is divisible by i, so we will hang out"<<endl;
}
};
int main()
{
Class1<42> f;
f.foo<24>();
return 0;
}
Upvotes: 4