chrisb2244
chrisb2244

Reputation: 3001

Constructor and functions using variadic templated parameters

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 ints and doubles, 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

Answers (2)

Cheers and hth. - Alf
Cheers and hth. - Alf

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

celticminstrel
celticminstrel

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

Related Questions