0

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

chrisb2244
  • 2,940
  • 22
  • 44
  • 1
    "How can I use the dimensions parameter pack that I've passed?" Well, that depends. How do you *want* to use the arguments? – T.C. Jul 16 '15 at 04:32
  • Perhaps it would be a good idea to post some code (or at least pseudo-code) indicating how you want to use them? – celticminstrel Jul 16 '15 at 04:33
  • In this (starting) case, I'd only need to get the values of the integers passed - I've edited in an example to show one way I've found to do that. If I had different types of variables though, this could lead to narrowing, or unavailable type conversions, or something? Similarly, if I wanted to have two constructors, one taking a list of `int`s, and one taking a list of `struct`s containing `int`s, and other values used with the `int`s, then presumably I'd need to be able to deduce the type passed to the function (or write two constructors, with an additional `enable_if`, perhaps?) – chrisb2244 Jul 16 '15 at 04:38
  • Doesn't compile with Visual C++ 2015. `foo.cpp(13): error C3547: template parameter 'unnamed-parameter' cannot be used because it follows a template parameter pack and cannot be deduced from the function parameters of 'templateClass::templateClass'` – Cheers and hth. - Alf Jul 16 '15 at 04:40
  • @Cheersandhth.-Alf MSVC bug :/ – T.C. Jul 16 '15 at 04:43
  • Not entirely sure what that error means but compiles and runs with g++ 4.9.2. Will post full example as edit. – chrisb2244 Jul 16 '15 at 04:43
  • You still haven't explained what you actually want to do with those values. "Apply a function to each value" is trivial, using the [pack expansion inside a braced initializer list](http://stackoverflow.com/questions/25680461/variadic-template-pack-expansion/25683817#25683817) trick. If you want more complex manipulations, you'll have to say what it is. – T.C. Jul 16 '15 at 05:00
  • 1
    In any event, that SFINAE seems overkill. Unless you are planning to inspect the constructibility of `Mesh`, a `static_assert` should be sufficient. – T.C. Jul 16 '15 at 05:02
  • [static_assert](http://en.cppreference.com/w/cpp/language/static_assert) looks like it might be the simpler answer. I had thought that this was dependent on a macro definition, but turns out I misremembered - that appears to be `assert` instead. An example of a function that I could use would be to get a single index from a group of indices - the common implementation being I think something along the lines of `idx = row*numCols + col;` or similar, but generalized to N-dimensional layouts. In any case, that also appears to be doable with a `initializer_list`. – chrisb2244 Jul 16 '15 at 05:10
  • [almost-static-if](https://rmf.io/cxx11/almost-static-if) link needs updating. – tukra Dec 30 '16 at 20:49
  • @tukra Thanks - updated the link – chrisb2244 Jan 10 '17 at 00:31

2 Answers2

1

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...);
}
celticminstrel
  • 1,637
  • 13
  • 21
  • It would appear that although this might be nice, I can't use a variable in the `tuple_element`. (i.e. `i` here is invalid, and I must instead write `0`, `1`, etc.) Is this a) true, and b) can I get around it somehow? – chrisb2244 Jul 16 '15 at 06:00
  • I think you should be able to use a variable as long as it can be evaluated as a compile-time constant. (For example, a template non-type parameter.) If you want to "iterate through" the types in the parameter pack, you'll need a recursive solution (as I did with `tuple_sum` there). – celticminstrel Jul 16 '15 at 06:18
  • Yeah, figured. Thanks for the suggestion - I suppose recursion it is. – chrisb2244 Jul 16 '15 at 06:37
  • 1
    `template constexpr auto tuple_sum(T first, Rest... rest) { std::common_type_t result = first; using expander = int[]; (void) expander {0, (result += rest, void(), 0)...}; return result; }` – T.C. Jul 16 '15 at 09:04
0

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
}
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • I don't have MSVC2015 to check, but I think that providing a member of the `enum class` and then using it as a default parameter to the typename might be approximately equivalent? An example is given for Clang 3.1 on the `static enable-if` page I linked in the question, which I think is meant to deal with the same problem that I suppose MSVC is complaining about. – chrisb2244 Jul 16 '15 at 05:06
  • @chrisb2244: I don't know what I was thinking by complicating things with an artificial base. Possibly lack of coffee or something to do with my diabetes. Updated the code in the example to the *conventional* SFINAE checking, there should be no reason to use any more fancy scheme? – Cheers and hth. - Alf Jul 16 '15 at 05:16
  • Do you still need the `IS_UNUSED` macro/struct setup with the `void` typename? – chrisb2244 Jul 16 '15 at 05:19
  • 1
    @chrisb2244: `IS_UNUSED` is just to avoid warnings for the unused local variable `dimensions`. Presumably that local variable will be used in the OP's final code. – Cheers and hth. - Alf Jul 16 '15 at 06:28