Limit the supported template class types with variadic templates
I'm trying to handle classes of image processing operations that are only compatible for certain combinations:
- measurement set [1,2,3, ...]
- set of types [int, float, double, ...]
One working method would be to define a generic templated class that handles the default case (which: does nothing):
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 this class for all supported combinations of types, for example, for example:
template<>
class IPOperation<2,float>
{
public:
void execute()
{
std::cout << "do something" << std::endl;
}
};
But since the number of supported size / type combinations can get really large, this approach is overkill.
Inspired by political design, it would be nice to equip the template class with certain constraints, 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 bad approach so far has been (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 am completely lost. It would be nice to have some kind of mechanism for doing some checks on how you use policies as delegates for certain strategies. Maybe my approach is wrong and the whole thing could be done much easier with macros, enums or dashes and std :: enable_if to make functions visible only for certain scenarios, but since I spent some time reading some C ++ 11 so, I don't know anything else.
Thanks guys for some helpful advice!
source to share
You can use something like the following if you want to allow a bogus operation (but not a 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>();
}
source to share
I am using SFINAE for this type of problem, for example:
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 slightly nicer error messages.
source to share
You can use boost::mpl::set
to check if a given size / type is supported. Use static_assert
if you want to reject unsupported types at compile time, or use SFINAE if you really want the "do nothing by default" logic.
static_assert
#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();
}
Printing
DoSomething
SFINAE
#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();
}
Printing
DoNothing
DoSomething
source to share