Reputation: 3001
I want to write a class which is templated by a number of dimensions:
namespace detail {
enum class enabler {};
}
template<size_t dim>
class templateClass
{
public:
template<class... DimArgs, typename std::enable_if<sizeof...(DimArgs)==dim, detail::enabler>::type...>
templateClass(DimArgs... dimensions) {
// Use integers passed to the constructor, one for each dimension
}
};
The detail::enabler
enum is found at this almost-static-if link. Here it is used as a second parameter pack, allowing 0 arguments to be passed. The scoped enum has no members, and can't(?) be accidentally passed.
They also use a using
declaration to swallow some of the typename
etc parts up, but I've typed in full to avoid having to read it there.
How can I use the dimensions
parameter pack that I've passed?
The class works well, eg:
templateClass<2> temp(5, 2); // works
templateClass<3> temp(5, 2, 4); // works
templateClass<1> temp(5,2); // would give compile-time error
but perhaps I've gotten a (or several) bad idea(s) of what I should use/do here?
Edit:
One solution I've found is to create a std::initializer_list
. I can create this with an int
or size_t
class, which works nicely here. However, if I don't know the type of the arguments being passed (for example, because my function can take both int
s and double
s, for some other purpose), is there a better way than:
std::initializer_list<int> list{dimensions...};
for (int i : list) {
std::cout << "i = " << i << std::endl;
}
Full working example:
Mesh.H:
#ifndef MESH_H
#define MESH_H
#include <type_traits>
#include <initializer_list>
#include <iostream>
namespace detail {
enum class enabler {};
}
template <bool Condition>
using EnableIf =
typename std::enable_if<Condition, detail::enabler>::type;
template<size_t meshDim>
class Mesh
{
public:
template<class... DimArgs, EnableIf<sizeof...(DimArgs)==meshDim>...>
Mesh(DimArgs... dimensions){
std::initializer_list<int> list{dimensions...};
for (int i : list) {
std::cout << "i = " << i << std::endl;
}
}
};
#endif // MESH_H
main.cpp:
#include <iostream>
#include "Mesh.H"
int main()
{
Mesh<2> mesh(5, 2);
return 0;
}
Compiles with g++ --std=c++11 main.cpp
Upvotes: 0
Views: 340
Reputation: 145204
To make the code portable you should not have the anonymous template parameter.
Here's code that compiles with MinGW g++ 5.1 and Visual C++ 2015:
#include <utility> // std::enable_if
#define IS_UNUSED( a ) (void) a; struct a
template< int dim >
class Tensor
{
public:
template< class... Args
, class Enabled_ = typename std::enable_if< sizeof...(Args) == dim, void >::type
>
Tensor( Args... args )
{
int const dimensions[] = { args... };
IS_UNUSED( dimensions );
}
};
auto main() -> int
{
Tensor<2> tempA(5, 2); // works
Tensor<3> tempB(5, 2, 4); // works
#ifdef FAIL
Tensor<1> temp(5,2); // would give compile-time error
#endif
}
Upvotes: 0
Reputation: 1679
It's possible to index the parameter pack by putting it in a tuple:
using T1 = std::tuple_element<0, std::tuple<DimArgs...>>; // Any index should work, not just 0
That doesn't quite solve the issue of possible numeric promotion or narrowing, though. I'm thinking something that amounts to decltype(tuple_sum(dimensions...))
would do the trick (provided you can assume they're numeric). It could look something like this (untested):
template<typename T>
constexpr T tuple_sum(T n) {return n;}
template<typename T, typename... Rest>
constexpr auto tuple_sum(T first, Rest... rest) {
return first + tuple_sum(rest...);
}
Upvotes: 1