I have a tuple of objects of different classes. I want to iterate over the tuple and call a certain method only if those class has one.
For example (pseudo-code):
struct A { int get( ) { return 5; }; };
struct B { };
struct C { int get( ) { return 10; }; };
int i = 0;
tuple<A, B, C> t;
for ( auto t_element : t )
{
if constexpr ( has_get_method( decltype(t_element) ) )
{
i += t_element.get( );
}
}
I already know how to iterate over the tuple and check if a class has some method using sfinae but how do I skip object that do not have the required method?
EDIT:
If you found this old question in the future, please know that this can be done much easier now using concepts from C++20 standard.
Just write a sfinae'd function and a catchall in case the previous one fails. You don't have to use if constexpr for that and you can't use it actually in C++14 (that is how you tagged the question).
Here is a minimal, working example:
#include <tuple>
#include <iostream>
auto value(...) { return 0; }
template <typename T>
auto value(T &t) -> decltype(t.get()) {
return t.get();
}
struct A { int get() { return 5; }; };
struct B {};
struct C { int get() { return 10; }; };
int main() {
int i = 0;
std::tuple<A, B, C> t;
i += value(std::get<0>(t));
i += value(std::get<1>(t));
i += value(std::get<2>(t));
std::cout << i << std::endl;
}
See it up and running on wandbox.
If you have any argument you want to use to test it, you can use std::forward as:
template <typename T, typename... Args>
auto value(T &t, Args&&... args)
-> decltype(t.get(std::forward<Args>(args)...))
{ return t.get(std::forward<Args>(args)...); }
Then invoke it as:
i += value(std::get<0>(t), params);
You can create a type-traits, to check if a class has a get() method, by declaring a couple of functions (no need to define them)
template <typename>
constexpr std::false_type withGetH (long);
template <typename T>
constexpr auto withGetH (int)
-> decltype( std::declval<T>().get(), std::true_type{} );
template <typename T>
using withGet = decltype( withGetH<T>(0) );
The following is a fully working c++17 example
#include <tuple>
#include <iostream>
#include <type_traits>
template <typename>
constexpr std::false_type withGetH (long);
template <typename T>
constexpr auto withGetH (int)
-> decltype( std::declval<T>().get(), std::true_type{} );
template <typename T>
using withGet = decltype( withGetH<T>(0) );
struct A { int get( ) { return 5; }; };
struct B { };
struct C { int get( ) { return 10; }; };
template <typename T>
int addGet (T & t)
{
int ret { 0 };
if constexpr ( withGet<T>{} )
ret += t.get();
return ret;
}
int main ()
{
int i = 0;
std::tuple<A, B, C> t;
i += addGet(std::get<0>(t));
i += addGet(std::get<1>(t));
i += addGet(std::get<2>(t));
std::cout << i << std::endl;
}
If you can't use if constexpr, you can write (in c++11/14) getAdd() as follows, using tag dispatching
template <typename T>
int addGet (T & t, std::true_type const &)
{ return t.get(); }
template <typename T>
int addGet (T & t, std::false_type const &)
{ return 0; }
template <typename T>
int addGet (T & t)
{ return addGet(t, withGet<T>{}); }
--EDIT--
The OP ask
my code needs to check for a templated method with parameters. Is it possible to modify your solution so that instead of int get() it could check for something like template<class T, class U> void process(T& t, U& u)?
I suppose it's possible.
You can create a type-traits withProcess2 (where 2 is the number of arguments) that receive three template type parameters: the class and the two template types
template <typename, typename, typename>
constexpr std::false_type withProcess2H (long);
template <typename T, typename U, typename V>
constexpr auto withProcess2H (int)
-> decltype( std::declval<T>().process(std::declval<U>(),
std::declval<V>()),
std::true_type{} );
template <typename T, typename U, typename V>
using withProcess2 = decltype( withProcess2H<T, U, V>(0) );
The following is a fully working example (c++17, but now you know how to make it c++14) with A and C with a process() with 2 template parameters and B with a process() with only 1 template parameter.
#include <iostream>
#include <type_traits>
template <typename, typename, typename>
constexpr std::false_type withProcess2H (long);
template <typename T, typename U, typename V>
constexpr auto withProcess2H (int)
-> decltype( std::declval<T>().process(std::declval<U>(),
std::declval<V>()),
std::true_type{} );
template <typename T, typename U, typename V>
using withProcess2 = decltype( withProcess2H<T, U, V>(0) );
struct A
{
template <typename T, typename U>
void process(T const &, U const &)
{ std::cout << "A::process(T, U)" << std::endl; }
};
struct B
{
template <typename T>
void process(T const &)
{ std::cout << "B::process(T)" << std::endl; }
};
struct C
{
template <typename T, typename U>
void process(T &, U &)
{ std::cout << "C::process(T, U)" << std::endl; }
};
template <typename T>
void callProcess (T & t)
{
static int i0 { 0 };
static long l0 { 0L };
if constexpr ( withProcess2<T, int &, long &>{} )
t.process(i0, l0);
}
int main ()
{
std::tuple<A, B, C> t;
callProcess(std::get<0>(t)); // print A::process(T, U)
callProcess(std::get<1>(t)); // no print at all
callProcess(std::get<2>(t)); // print C::process(T, U)
}
Within a class, I have two different methods which should be mutually exclusive depending on the caller template parameter.
class Foo
{
// For collections
template<class T>
typename boost::enable_if<boost::is_same<typename std::vector<typename T::value_type>, T>::value, const T&>::type
doSomething()
{ }
// For single types
template<class T>
typename boost::enable_if<!boost::is_same<typename std::vector<typename T::value_type>, T>::value, const T&>::type
doSomething()
{ }
}
This won't compile.
error: type/value mismatch at argument 1 in template parameter list for 'template struct boost::enable_if'
error: expected a type, got '! boost::is_same::value'
How about:
template <typename T> struct is_std_vector : std::false_type {};
template <typename T, typename A>
struct is_std_vector<std::vector<T, A>> : std::true_type {};
And then
class Foo
{
// For collections
template<class T>
typename std::enable_if<is_std_vector<T>::value, const T&>::type
doSomething();
// For single types
template<class T>
typename std::enable_if<!is_std_vector<T>::value, const T&>::type
doSomething();
};
Unlike std's version, boost::enable_if accepts a type (kinda wrapper under boolean value), so you should write something like
class Foo
{
// For collections
template<class T>
typename boost::enable_if<
typename boost::is_same<typename std::vector<typename T::value_type>, T>,
const T&>::type doSomething()
{ }
// For single types
template<class T>
typename boost::enable_if_с<
!boost::is_same<typename std::vector<typename T::value_type>, T>::value,
const T&>::type doSomething()
{ }
}
Note here, I've used typename before boost::is_same and haven't used ::value in the first specification. On the contrary, I had to use enable_if_с in the second overload, because ! operator isn't applicable to a type.
What about a sort of tag dispatching?
#include <vector>
#include <iostream>
template <typename, typename>
struct isSame
{ typedef int type; };
template <typename T>
struct isSame<T, T>
{ typedef long type; };
struct foo
{
template <typename T>
T const & doSomething (T const & t, int)
{ std::cout << "int version" << std::endl; return t; }
template <typename T>
T const & doSomething (T const & t, long)
{ std::cout << "long version" << std::endl; return t; }
template <typename T>
T const & doSomething (T const & t)
{ return doSomething(t, typename isSame<
typename std::vector<typename T::value_type>, T>::type()); }
};
int main ()
{
foo f;
std::vector<int> v;
f.doSomething(v); // print "long version"
}
If what you want is to overload the function based on whether you are given a vector or not
#include <type_traits>
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
class Foo {
public:
// For collections
template <class T>
const vector<T>& do_something(const std::vector<T>& input) {
cout << __PRETTY_FUNCTION__ << endl;
return input;
}
// For single types
template <class T>
const T& do_something(const T& input) {
cout << __PRETTY_FUNCTION__ << endl;
return input;
}
};
int main() {
auto foo = Foo{};
auto v = std::vector<int>{};
auto i = int{};
foo.do_something(v);
foo.do_something(i);
}
If you want to be even more general and check for any instantiated type
#include <type_traits>
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
namespace {
template <typename T, template <typename...> class TT>
struct IsInstantiationOf
: public std::integral_constant<bool, false> {};
template <template <typename...> class TT, typename... Args>
struct IsInstantiationOf<TT<Args...>, TT>
: public std::integral_constant<bool, true> {};
} // namespace anonymous
class Foo {
public:
// For collections
template <typename VectorType, typename std::enable_if_t<IsInstantiationOf<
std::decay_t<VectorType>, std::vector>::value>* = nullptr>
void do_something(VectorType&&) {
cout << "Vector overload" << endl;
}
// For single types
template <class T, typename std::enable_if_t<!IsInstantiationOf<
std::decay_t<T>, std::vector>::value>* = nullptr>
void do_something(T&&) {
cout << "Non vector overload" << endl;
}
};
int main() {
auto foo = Foo{};
auto v = std::vector<int>{};
auto i = int{};
foo.do_something(v);
foo.do_something(i);
}
Also please note that you should avoid putting std::enable_if in the function signature as much as possible for these reasons https://stackoverflow.com/a/14623831/5501675
I'm writing a contains() utility function and have come up with this. My question is: Is there a nicer way to select the right function to handle the call?
template <class Container>
inline auto contains(Container const& c,
typename Container::key_type const& key, int) noexcept(
noexcept(c.end(), c.find(key))) ->
decltype(c.find(key), true)
{
return c.end() != c.find(key);
}
template <class Container>
inline auto contains(Container const& c,
typename Container::value_type const& key, long) noexcept(
noexcept(c.end(), ::std::find(c.begin(), c.end(), key))
)
{
auto const cend(c.cend());
return cend != ::std::find(c.cbegin(), cend, key);
}
template <class Container, typename T>
inline auto contains(Container const& c, T const& key) noexcept(
noexcept(contains(c, key, 0))
)
{
return contains(c, key, 0);
}
For the record, you could write:
#include "magic.h"
template <typename T, typename... Us>
using has_find = decltype(std::declval<T>().find(std::declval<Us>()...));
template <class Container, typename T>
auto contains(const Container& c, const T& key)
{
return static_if<detect<has_find, decltype(c), decltype(key)>{}>
(
[&] (auto& cont) { return cont.end() != cont.find(key); },
[&] (auto& cont) { return cont.end() != std::find(cont.begin(), cont.end(), key); }
)(c);
}
where magic.h contains:
#include <type_traits>
template <bool> struct tag {};
template <typename T, typename F>
auto static_if(tag<true>, T t, F f) { return t; }
template <typename T, typename F>
auto static_if(tag<false>, T t, F f) { return f; }
template <bool B, typename T, typename F>
auto static_if(T t, F f) { return static_if(tag<B>{}, t, f); }
template <bool B, typename T>
auto static_if(T t) { return static_if(tag<B>{}, t, [](auto&&...){}); }
template <typename...>
using void_t = void;
template <typename AlwaysVoid, template <typename...> class Operation, typename... Args>
struct detect_impl : std::false_type {};
template <template <typename...> class Operation, typename... Args>
struct detect_impl<void_t<Operation<Args...>>, Operation, Args...> : std::true_type {};
template <template <typename...> class Operation, typename... Args>
using detect = detect_impl<void, Operation, Args...>;
DEMO
namespace details {
template<template<class...>class Z, class, class...Ts>
struct can_apply:std::false_type{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z,std::void_t<Z<Ts...>>,Ts...>:std::true_type{};
};
template<template<class...>class Z, class...Ts>
using can_apply=typename details::can_apply<Z,void,Ts...>::type;
this takes a template and arguments, and tells you if you can apply it.
template<class T, class...Args>
using dot_find_r = decltype(std::declval<T>().find(std::declval<Args>()...));
template<class T, class...Args>
constexpr can_apply<dot_find_r, T, Args...> can_dot_find{};
we now tag dispatch on myfind:
template<class C>
using iterator = decltype( ::std::begin(std::declval<C>()) );
namespace details {
template<class Container, class Key>
iterator<Container const&> myfind(
std::false_type can_dot_find,
Container const& c,
Key const& key
)
noexcept(
noexcept( ::std::find(::std::begin(c), ::std::end(c), key) )
)
{
return ::std::find( ::std::begin(c), ::std::end(c), key );
}
template <class Container, class Key>
iterator<Container const&> myfind(
std::true_type can_dot_find,
Container const& c,
Key const& key
) noexcept(
noexcept( c.find(key) )
)
{
return c.find(key);
}
}
template<class Container, class Key>
iterator<Container const&> myfind(
Container const& c,
Key const& k
) noexcept (
details::myfind( can_dot_find<Container const&, Key const&>, c, k )
)
{
return details::myfind( can_dot_find<Container const&, Key const&>, c, k );
}
template<class Container, class Key>
bool contains(
Container const& c,
Key const& k
) noexcept (
noexcept( ::std::end(c), myfind( c, k ) )
)
{
return myfind(c, k) != ::std::end(c);
}
As a bonus, the above version works with raw C style arrays.
The next enhancement I'd do would be an auto-ADL std::begin to make begin extensions work in the non-dot_find case.
My personal equivalent returns a std::optional<iterator> of the appropriate type. This both provides a quick "is it there", and gives easy access to the iterator if not not there.
if (auto oit = search_for( container, key )) {
// use *oit here as the iterator to the element, guaranteed not to be `end`
}
or
if (search_for( container, key )) {
// key was there
}
but that is neither here nor there.
So you want to call c.find if possible else std::find. But also being wary of type ambiguity as in std::set.
Here is the code to solve that (with the verbose and micro-optimization removed in favor of readability):
#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
#include <type_traits>
#include <set>
#include <cstdarg>
using namespace std;
template <typename T, typename Ret>
struct dummy {
typedef Ret type;
};
template <class Container>
auto contains(const Container &c, typename Container::key_type const &key) ->
typename dummy<decltype(c.find(key)), bool>::type {
cout << "c.find" << endl;
return c.end() != c.find(key);
}
template <class Container, typename ...T>
typename std::enable_if<sizeof...(T)==1, bool>::type contains(const Container &c, const T&... args) {
typename Container::value_type const &val = std::get<0>(std::tuple<const T&...>(args...));
cout << "std::find" << endl;
return c.cend() != find(c.cbegin(), c.cend(), val);
}
int main() {
vector<int> v = {1,2,3};
cout << contains(v,4) << contains(v,2) << endl;
map<int, int> m;
m[1] = 1;
m[2] = 2;
m[3] = 3;
cout << contains(m,4) << contains(m,2) << endl;
set<int> s;
cout << contains(s,4) << contains(s,2) << endl;
return 0;
}
What I did:
I made the first contains function dependent on c.find() being callable. When it's not, the compiler doesn't see the function, and no issues arise
I resolved ambiguity when key_type and value_type are the same, by introducing the function using std::find with its second mandatory argument as a variadic template. I also forced the variadic template of being of size 1.
If you just assume that key_type existing means that container.find exists as in OP, then you can simplify the code and remove the dummy structure:
template <class Container>
bool contains(const Container &c, typename Container::key_type const &key)
{
return c.end() != c.find(key);
}
template <class Container, typename ...T>
typename std::enable_if<sizeof...(T)==1, bool>::type contains(const Container &c, const T&... args) {
typename Container::value_type const &val = std::get<0>(std::tuple<const T&...>(args...));
return c.cend() != find(c.cbegin(), c.cend(), val);
}
Instead of having to resolve ambiguity that way, it's possible to disable the second function altogether if Container::find does exist. This and That answer both show different ways of knowing so. Then using std::enable_if<! (Does Container have the find method?) , bool>::type as the return type of the second function will work.
since find is exist in associative containers then you can explicitly make them as true types.
meta-functions:
template <class> struct has_find_impl:std::false_type{};
template <class T, class... Args> struct has_find_impl<std::set<T, Args...>>:std::true_type{};
template <class T, class... Args> struct has_find_impl<std::map<T, Args...>>:std::true_type{};
template <class T, class... Args> struct has_find_impl<std::multiset<T, Args...>>:std::true_type{};
template <class T, class... Args> struct has_find_impl<std::multimap<T, Args...>>:std::true_type{};
template <class T> using has_find = has_find_impl<typename std::decay<T>::type>;
and use it like so:
template <class Container>
bool contains_impl(const Container& c, const typename Container::key_type& key, std::true_type)
{
return c.find(key) != c.cend();
}
template <class Container>
bool contains_impl(const Container& c, typename Container::const_reference key, std::false_type)
{
return std::find(c.cbegin(), c.cend(), key) != c.cend();
}
template <class Container, class T>
bool contains(const Container& c, const T& key)
{
return contains_impl(c, key, has_find<Container>{});
}
or used it with SFINAE. here a complete example:
#include <iostream>
#include <algorithm>
#include <utility>
#include <map>
#include <set>
#include <vector>
#include <array>
#include <type_traits>
template <class> struct has_find_impl:std::false_type{};
template <class T, class... Args> struct has_find_impl<std::set<T, Args...>>:std::true_type{};
template <class T, class... Args> struct has_find_impl<std::map<T, Args...>>:std::true_type{};
template <class T, class... Args> struct has_find_impl<std::multiset<T, Args...>>:std::true_type{};
template <class T, class... Args> struct has_find_impl<std::multimap<T, Args...>>:std::true_type{};
template <class T> using has_find = has_find_impl<typename std::decay<T>::type>;
template <class Container>
typename std::enable_if<has_find<Container>::value, bool>::type
contains_impl(const Container& c, const typename Container::key_type& key)
{
return c.find(key) != c.cend();
}
template <class Container>
typename std::enable_if<!has_find<Container>::value, bool>::type
contains_impl(const Container& c, typename Container::const_reference key)
{
return std::find(c.cbegin(), c.cend(), key) != c.cend();
}
template <class Container, class T>
bool contains(const Container& c, const T& key)
{
return contains_impl(c, key);
}
int main()
{
std::cout << std::boolalpha;
std::array<int, 3> a = {{ 1, 2, 3 }};
std::cout << contains(a, 0) << "\n";
std::cout << contains(a, 1) << "\n\n";
std::vector<int> v = { 1, 2, 3 };
std::cout << contains(v, 0) << "\n";
std::cout << contains(v, 1) << "\n\n";
std::set<int> s = { 1, 2, 3 };
std::cout << contains(s, 0) << "\n";
std::cout << contains(s, 1) << "\n\n";
std::map<int, int> m = { { 1, 1}, { 2, 2}, { 3, 3} };
std::cout << contains(m, 0) << "\n";
std::cout << contains(m, 1) << "\n\n";
}
Consider the following code:
#include <iostream>
#include <array>
template <typename, int, int...> struct NArray;
template <typename T, int NUM_DIMENSIONS, int N>
struct NArray<T, NUM_DIMENSIONS, N> {
using type = std::array<T, N>;
};
template <typename T, int NUM_DIMENSIONS, int FIRST, int... REST>
struct NArray<T, NUM_DIMENSIONS, FIRST, REST...> {
using type = std::array<typename NArray<T, NUM_DIMENSIONS, REST...>::type, FIRST>;
};
template <typename T, int NUM_DIMENSIONS, int... N>
typename NArray<T, NUM_DIMENSIONS, N...>::type NDimensionalArray() {
typename NArray<T, NUM_DIMENSIONS, N...>::type nArray;
return nArray;
}
int main() {
const auto nArray = NDimensionalArray<int,4, 2,4,5,3>();
}
What I want is to be able to extend the template pack of NDimensionalArray with more int values so that certain values are initialized to some specified fixed value. For example,
auto a = NDimensionalArray<bool,4, 2,4,5,3, 1,2,3,2, 0,0,2,1>(true);
will return a 2x4x5x3 4-dimensional std::array with a[1][2][3][2] = true and a[0][0][2][1] = true, and every other element false. But I'm having issues with multiple template packs and can't seem to get it working. Any help would be appreciated. Thanks.
Well here's a working solution. If somebody can improve upon it, I would be very interested in seeing it because I don't know any other way to do it.
#include <iostream>
#include <array>
#include <cstring>
template <int... > struct seq {};
template <typename, int...> struct NArray;
template <typename T, int N>
struct NArray<T, N> {
using type = std::array<T, N>;
};
template <typename T, int FIRST, int... REST>
struct NArray<T, FIRST, REST...> {
using type = std::array<typename NArray<T, REST...>::type, FIRST>;
};
template <typename T, typename Dim>
struct make_narray;
template <typename T, int... N>
struct make_narray<T, seq<N...>>
{
using type = typename NArray<T, N...>::type;
};
template <typename T>
T& get(T& val, seq<>)
{
return val;
}
template <typename NA, int E0, int... Es>
auto get(NA& arr, seq<E0, Es...>)
-> decltype(get(arr[E0], seq<Es...>{}))
{
return get(arr[E0], seq<Es...>{});
}
template <typename T, typename Dim, typename... Elems>
typename make_narray<T, Dim>::type
NDimensionalArray(T val)
{
typename make_narray<T, Dim>::type narray{};
auto _{get(narray, Elems{}) = val ...}; // Quick initialization step!
return narray;
}
int main() {
auto a = NDimensionalArray<bool, seq<2, 4, 5, 3>, seq<1, 2, 3, 2>, seq<0, 0, 2, 1>>(true);
std::cout << std::boolalpha;
std::cout << a[0][0][0][0] << std::endl; // prints false
std::cout << a[1][2][3][2] << std::endl; // prints true
std::cout << a[0][0][2][1] << std::endl; // prints true
}
The exact syntax you wanted NDimensionalArray<bool,4, 2,4,5,3, 1,2,3,2, 0,0,2,1>(true), in both C++14 and C++11 (second demo):
#include <iostream>
#include <iomanip>
#include <array>
#include <tuple>
#include <utility>
#include <type_traits>
#include <cstddef>
template <typename, int, int...> struct NArray;
template <typename T, int NUM_DIMENSIONS, int N>
struct NArray<T, NUM_DIMENSIONS, N>
{
using type = std::array<T, N>;
};
template <typename T, int NUM_DIMENSIONS, int FIRST, int... REST>
struct NArray<T, NUM_DIMENSIONS, FIRST, REST...>
{
using type = std::array<typename NArray<T, NUM_DIMENSIONS, REST...>::type, FIRST>;
};
template <typename A, typename T>
void assign(A& arr, const T& value)
{
arr = value;
}
template <int I, int... Is, typename A, typename T>
void assign(A& arr, const T& value)
{
assign<Is...>(arr[I], value);
}
template <int SIZE, int PACK, int... Ind, typename T, typename A, std::size_t... Is>
auto set(const T& value, A& arr, std::index_sequence<Is...> seq)
-> std::enable_if_t<(SIZE*PACK == sizeof...(Ind))>
{
}
template <int SIZE, int PACK, int... Ind, typename T, typename A, std::size_t... Is>
auto set(const T& value, A& arr, std::index_sequence<Is...> seq)
-> std::enable_if_t<(SIZE*PACK < sizeof...(Ind))>
{
constexpr auto t = std::make_tuple(Ind...);
assign<std::get<PACK*SIZE+Is>(t)...>(arr, value);
set<SIZE, PACK+1, Ind...>(value, arr, seq);
}
template <typename T, int DIMS, int... N, std::size_t... Is>
auto make_narray(const T& value, std::index_sequence<Is...> seq)
{
constexpr auto t = std::make_tuple(N...);
typename NArray<T, DIMS, std::get<Is>(t)...>::type arr{};
set<DIMS, 1, N...>(value, arr, seq);
return arr;
}
template <typename T, int DIMS, int... N>
auto NDimensionalArray(const T& value)
{
return make_narray<T, DIMS, N...>(value, std::make_index_sequence<DIMS>{});
}
int main()
{
auto a = NDimensionalArray<bool,4, 2,4,5,3, 1,2,3,2, 0,0,2,1>(true);
std::cout << std::boolalpha;
std::cout << a[1][2][3][2] << std::endl; // ~~~~^
std::cout << a[0][0][2][1] << std::endl; // ~~~~~~~~~~~~^
std::cout << a[0][0][0][0] << std::endl; // (not set)
}
Output:
true
true
false
DEMO (C++14)
DEMO 2 (C++11)
Solution with the initializing positions in the argument pack ARGS&&... args instead:
#include <array>
#include <iostream>
#include <deque>
template <typename, std::size_t...> struct NArray;
template <typename T, std::size_t N>
struct NArray<T,N> {
using type = std::array<T,N>;
};
template <typename T, std::size_t First, std::size_t... Rest>
struct NArray<T, First, Rest...> {
using type = std::array<typename NArray<T, Rest...>::type, First>;
};
template <typename E, typename Container, typename T>
void assign (E& element, Container&&, const T& v) { element = v; }
template <typename Subarray, std::size_t N, typename Container, typename T>
void assign (std::array<Subarray, N>& narray, Container&& pos, const T& v) {
const std::size_t index = pos.front();
pos.pop_front();
assign (narray[index], pos, v);
}
template <typename T, int... Dimensions, typename... Args>
typename NArray<T, Dimensions...>::type NDimensionalArray (const T& value, Args&&... args) {
typename NArray<T, Dimensions...>::type narray{};
const auto initializer = {std::forward<Args>(args)...};
const int groupSize = sizeof...(Dimensions), numGroups = initializer.size() / groupSize;
for (std::size_t i = 0; i < numGroups; i++)
assign (narray, std::deque<std::size_t>(initializer.begin() + i*groupSize, initializer.begin() + (i+1)*groupSize), value);
return narray;
}
int main() {
const auto multiArray = NDimensionalArray<double, 5,6,7,8,9> (3.14, 1,2,3,2,4, 3,3,2,1,2, 0,1,3,1,2);
std::cout << multiArray[1][2][3][2][4] << '\n'; // 3.14
std::cout << multiArray[3][3][2][1][2] << '\n'; // 3.14
std::cout << multiArray[0][1][3][1][2] << '\n'; // 3.14
}
Here is Piotr's solution tidied up a bit, by removing his enable_if specializations and using the index trick once again instead. Also, I've generalized to the following example syntax for any number of set values:
makeNDimensionalArray<char, I<3,6,5,4>, I<2,4,3,2, 0,1,2,3, 1,2,4,3>, I<0,0,0,0, 2,3,1,2>, I<1,1,2,1>>('a','b','c')
where I<3,6,5,4> sets the multi-array's dimensions. Then I<2,4,3,2, 0,1,2,3, 1,2,4,3> sets those three indexed positions of the array to 'a', I<0,0,0,0, 2,3,1,2> sets those two indexed positions of the array to 'b', and so forth.
#include <iostream>
#include <array>
#include <tuple>
#include <utility>
template <typename, std::size_t, std::size_t...> struct NArray;
template <typename T, std::size_t NumDimensions, std::size_t N>
struct NArray<T, NumDimensions, N> {
using type = std::array<T, N>;
};
template <typename T, std::size_t NumDimensions, std::size_t First, std::size_t... Rest>
struct NArray<T, NumDimensions, First, Rest...> {
using type = std::array<typename NArray<T, NumDimensions, Rest...>::type, First>;
};
template <typename T, std::size_t... Dimensions>
using NDimensionalArray = typename NArray<T, sizeof...(Dimensions), Dimensions...>::type;
template <typename T, typename Dimensions> struct NArrayFromPack;
template <typename T, template <std::size_t...> class P, std::size_t... Dimensions>
struct NArrayFromPack<T, P<Dimensions...>> : NArray<T, sizeof...(Dimensions), Dimensions...> {
static constexpr std::size_t num_dimensions = sizeof...(Dimensions);
};
template <typename A, typename T>
void setArrayValue (A& a, const T& t) { a = t; }
template <std::size_t First, std::size_t... Rest, typename Array, typename T>
void setArrayValue (Array& array, const T& t) {
setArrayValue<Rest...>(array[First], t);
}
template <typename Indices, typename Sequence> struct InitializeArray;
template <template <std::size_t...> class P, std::size_t... Is, std::size_t... Js>
struct InitializeArray<P<Is...>, std::index_sequence<Js...>> {
template <typename Array, typename T>
static void execute (Array& array, const T& t) {
constexpr std::size_t GroupSize = sizeof...(Js), NumGroups = sizeof...(Is) / GroupSize;
set<GroupSize>(array, t, std::make_index_sequence<NumGroups>{});
}
private:
template <std::size_t GroupSize, typename Array, typename T, std::size_t... Ks>
static void set (Array& array, const T& t, std::index_sequence<Ks...>) {
const int dummy[] = {(do_set<Ks, GroupSize>(array, t), 0)...};
static_cast<void>(dummy);
}
template <std::size_t N, std::size_t GroupSize, typename Array, typename T>
static void do_set (Array& array, const T& t) {
constexpr std::size_t a[] = {Is...};
setArrayValue<a[N*GroupSize + Js]...>(array, t);
}
};
template <typename T, typename Dimensions, typename... Indices, typename... Args>
auto makeNDimensionalArray (const Args&... args) {
using A = NArrayFromPack<T, Dimensions>;
typename A::type array;
const int a[] = {(InitializeArray<Indices, std::make_index_sequence<A::num_dimensions>>::execute(array, args), 0)...};
static_cast<void>(a);
return array;
}
template <std::size_t...> struct I;
int main() {
const NDimensionalArray<char, 3,6,5,4> a = makeNDimensionalArray<char, I<3,6,5,4>, I<2,4,3,2, 0,1,2,3, 1,2,4,3>, I<0,0,0,0, 2,3,1,2>, I<1,1,2,1>>('a','b','c');
std::cout << a[2][4][3][2] << std::endl; // a
std::cout << a[0][1][2][3] << std::endl; // a
std::cout << a[1][2][4][3] << std::endl; // a
std::cout << a[0][0][0][0] << std::endl; // b
std::cout << a[2][3][1][2] << std::endl; // b
std::cout << a[1][1][2][1] << std::endl; // c
}