Reputation: 1442
I'm trying to handle image processing operation classes which are only compatible for certain combinations of:
One working method would be to define a generic template class which handles the default-case (which is: do nothing) like this:
template <int dimension, typename dataType>
class IPOperation
{
public:
void execute()
{
std::cout << "do nothing" << std::endl;
}
};
Then I would have to write a specialization of that class for all supported type combinations, like for instance:
template<>
class IPOperation<2,float>
{
public:
void execute()
{
std::cout << "do something" << std::endl;
}
};
But since the number of supported dimension/type combinations can become really big, this approach is a total overkill..
Inspired by policy driven design it would be nice to equip a template class with specific restrictions, something like this:
template <int dimension, typename dataType>
class IPOperation : public supportedDimensions<2,3,4>, public supportedTypes<int, float, double>
{
void execute()
{
std::cout << "execute()" << std::endl;
}
};
My poor approach so far was this (and it seems pretty bad, I can't help myself):
#include <iostream>
#include <vector>
template <int... supportedDimensions>
class IPDimensions
{
private:
public:
static std::vector<int> getSupportedDimensions()
{
return { supportedDimensions... };
}
static bool supportsDimension(int dim)
{
std::vector<int> dimensions = getSupportedDimensions();
return std::find(dimensions.begin(), dimensions.end(), dim) != dimensions.end();
}
};
template <int dim>
class IPOperation : public IPDimensions<2,3>
{
private:
public:
void execute(void)
{
if(IPOperation::supportsDimension(dim))
{
std::cout << dim << "d is supported -> execute" << std::endl;
}
else
{
std::cout << dim << "d is not supported -> sit down and do nothing" << std::endl;
}
}
};
int main(int argc, const char * argv[])
{
IPOperation<2>* okay = new IPOperation<2>();
IPOperation<4>* notOkay = new IPOperation<4>();
okay->execute();
notOkay->execute();
}
When trying to apply something like this for types, I'm totally lost. It would be nice to have some sort of mechanism to do some checking like you use policies as delegates for specific strategies. Maybe my approach is the wrong way and this whole thing could be achieved much simpler via macros, enums or traits and std::enable_if to make functions only visible for defined scenarios, but since I spent some time reading through some c++11 topics, I'm really not sure about anything anymore.
Thank you guys in advance for any helpful advice!
Upvotes: 3
Views: 661
Reputation: 7904
You can leverage boost::mpl::set
to check the if a given dimension/type is supported. Use static_assert
if you want to reject unsupported types at compile-time or use SFINAE if you actually want the "do nothing by default" logic.
#include <iostream>
#include <boost/mpl/set.hpp>
#include <boost/mpl/set_c.hpp>
using namespace boost::mpl;
template <std::size_t Dim, typename Type>
struct Op {
static_assert(has_key<set_c<std::size_t, 2, 3, 4>,
integral_c<std::size_t, Dim>>::value,
"Unsupported dimension!");
static_assert(has_key<set<int, float, double>, Type>::value,
"Unsupported type!");
void Execute() {
std::cout << "DoSomething" << std::endl;
}
};
int main() {
// Op<1, int> x; // error: Unsupported dimension!
// Op<2, std::string> x; // error: Unsupported type!
Op<2, int> x;
x.Execute();
}
Prints:
DoSomething
#include <iostream>
#include <boost/mpl/set.hpp>
#include <boost/mpl/set_c.hpp>
using namespace boost::mpl;
template <std::size_t Dim, typename Type, typename = void>
struct Op {
void Execute() {
std::cout << "DoNothing" << std::endl;
}
};
template <std::size_t Dim, typename Type>
struct Op<Dim,
Type,
std::enable_if_t<has_key<set_c<std::size_t, 2, 3, 4>,
integral_c<std::size_t, Dim>>::value &&
has_key<set<int, float, double>, Type>::value>> {
void Execute() {
std::cout << "DoSomething" << std::endl;
}
};
int main() {
Op<1, int> x;
Op<2, int> y;
x.Execute();
y.Execute();
}
Prints:
DoNothing
DoSomething
Upvotes: 0
Reputation: 3338
You can use something like following if you want to allow dummy operation (but no compilation error) for unsupported types:
#include <type_traits>
#include <iostream>
template <class...>
struct supportedTypes {
template <class X>
static constexpr bool check() { return false; };
};
template <class A, class... R>
struct supportedTypes<A, R...> {
template <class X>
static constexpr bool check() {
return std::is_same<X, A>::value
|| supportedTypes<R...>::template check<X>(); }
};
int main() {
std::cout << supportedTypes<int,double>::check<int>();
std::cout << supportedTypes<int,double>::check<void>();
}
Upvotes: 1
Reputation: 45424
I use either SFINAE for this type of problem, for example like this:
template<std::size_t dimensions, typename dataType, typename=void> class IPOperation;
template<std::size_t dimensions, typename dataType>
class IPOperation<dimensions, dataType,
std::enable_if<(0<dimensions && dimensions<4) &&
std::is_floatingpoint<dataType>::value >::type>
{
/* ... */
};
IPOperation<2,float> a; // okay
IPOperation<4,float> b; // compile-time error: wrong dimensionality
IPOperation<3,int> c; // compile-time error: wrong dataType
or static_assert
:
template<std::size_t dimensions, typename dataType>
class IPOperation<dimensions, dataType>
{
static_assert(0<dimensions && dimensions<4,"wrong dimension");
static_assert(std::is_floatingpoint<dataType>::value,"incompatible data type");
/* ... */
};
which gives somewhat nicer error messages.
Upvotes: 1