The title is bad but I couldn't come up with anything better. Feel free to change it.
Here's a template multidimensional array class that I'm currently working on. I'm trying to optimise it as much as I can:
#include <array>
template <typename T, std::size_t... Dimensions>
class multidimensional_array
{
public:
using value_type = T;
using size_type = std::size_t;
private:
template<typename = void>
static constexpr size_type multiply(void)
{
return 1u;
}
template<std::size_t First, std::size_t... Other>
static constexpr size_type multiply(void)
{
return First * multidimensional_array::multiply<Other...>();
}
public:
using container_type = std::array<value_type, multidimensional_array::multiply<Dimensions...>()>;
using reference = value_type &;
using const_reference = value_type const&;
using iterator = typename container_type::iterator;
private:
container_type m_data_array;
template<typename = void>
static constexpr size_type linearise(void)
{
return 0u;
}
template<std::size_t First, std::size_t... Other>
static constexpr size_type linearise(std::size_t index, std::size_t indexes...)
{
return multidimensional_array::multiply<Other...>()*index + multidimensional_array::linearise<Other...>(indexes);
}
public:
// Constructor
explicit multidimensional_array(const_reference value = value_type {})
{
multidimensional_array::fill(value);
}
// Accessors
reference operator()(std::size_t indexes...)
{
return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
}
const_reference operator()(std::size_t indexes...) const
{
return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
}
// Iterators
iterator begin()
{
return m_data_array.begin();
}
iterator end()
{
return m_data_array.end();
}
// Other
void fill(const_reference value)
{
m_data_array.fill(value);
}
};
My main function is
int main(void)
{
multidimensional_array<int, 2u, 3u, 4u, 5u, 6u> foo;
int k = 0;
for (auto& s : foo)
s = k++;
//std::cout << foo(0u, 0u, 0u, 1u, 0u) << std::endl;
return 0;
}
The above code compilers without warning/error. As soon as I uncomment the std::cout part though, I get this:
g++-7 -std=c++17 -o foo.o -c foo.cpp -Wall -Wextra -pedantic
foo.cpp: In instantiation of ‘multidimensional_array<T, Dimensions>::value_type& multidimensional_array<T, Dimensions>::operator()(std::size_t, ...) [with T = int; long unsigned int ...Dimensions = {2, 3, 4, 5, 6}; multidimensional_array<T, Dimensions>::reference = int&; multidimensional_array<T, Dimensions>::value_type = int; std::size_t = long unsigned int]’:
foo.cpp:99:37: required from here
foo.cpp:60:72: error: no matching function for call to ‘multidimensional_array<int, 2, 3, 4, 5, 6>::linearise<2, 3, 4, 5, 6>(std::size_t&)’
return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
foo.cpp:38:30: note: candidate: template<class> static constexpr multidimensional_array<T, Dimensions>::size_type multidimensional_array<T, Dimensions>::linearise() [with <template-parameter-2-1> = <template-parameter-1-1>; T = int; long unsigned int ...Dimensions = {2, 3, 4, 5, 6}]
static constexpr size_type linearise(void)
^~~~~~~~~
foo.cpp:38:30: note: template argument deduction/substitution failed:
foo.cpp:60:72: error: wrong number of template arguments (5, should be at least 0)
return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
foo.cpp:44:30: note: candidate: template<long unsigned int First, long unsigned int ...Other> static constexpr multidimensional_array<T, Dimensions>::size_type multidimensional_array<T, Dimensions>::linearise(std::size_t, std::size_t, ...) [with long unsigned int First = First; long unsigned int ...Other = {Other ...}; T = int; long unsigned int ...Dimensions = {2, 3, 4, 5, 6}]
static constexpr size_type linearise(std::size_t index, std::size_t indexes...)
^~~~~~~~~
foo.cpp:44:30: note: template argument deduction/substitution failed:
foo.cpp:60:72: note: candidate expects 2 arguments, 1 provided
return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
Makefile:17: recipe for target 'foo.o' failed
make: *** [foo.o] Error 1
And I know why now. My question is, how can I fix linearise so that it can pass indexes without going through va_list and such? Unfortunately linearise is already a template, variadic function, so I can't use variadic template shenanigans in that regard.
As in preceding question the problem is that the following signatures
template<std::size_t First, std::size_t... Other>
static constexpr size_type linearise(std::size_t index,
std::size_t indexes...)
reference operator()(std::size_t indexes...)
const_reference operator()(std::size_t indexes...) const
aren't what do you mean (indexes a variadic list of std::size_t) but are exactly equivalent to
template<std::size_t First, std::size_t... Other>
static constexpr size_type linearise(std::size_t index,
std::size_t indexes,
...)
reference operator()(std::size_t indexes, ...)
const_reference operator()(std::size_t indexes, ...) const
where indexes is a single std::size_t followed by a C-style optional sequence of argument.
A simple solution (you tagged C++17 but is available starting from C++11) is based on the use of variadic templates.
By example, as follows
template <std::size_t First, std::size_t ... Other, typename ... Ts>
static constexpr size_type linearise (std::size_t index,
Ts ... indexes)
{ return multidimensional_array::multiply<Other...>() * index
+ multidimensional_array::linearise<Other...>(indexes...); }
// Accessors
template <typename ... Ts>
reference operator() (Ts ... indexes)
{ return m_data_array[
multidimensional_array::linearise<Dimensions...>(indexes...)]; }
template <typename ... Ts>
const_reference operator() (Ts ... indexes) const
{ return m_data_array[
multidimensional_array::linearise<Dimensions...>(indexes...)]; }
The following is you're code, modified and compilable
#include <array>
#include <iostream>
template <typename T, std::size_t ... Dimensions>
class multidimensional_array
{
public:
using value_type = T;
using size_type = std::size_t;
private:
template <typename = void>
static constexpr size_type multiply ()
{ return 1u; }
template <std::size_t First, std::size_t ... Other>
static constexpr size_type multiply(void)
{ return First * multidimensional_array::multiply<Other...>(); }
public:
using container_type = std::array<value_type,
multidimensional_array::multiply<Dimensions...>()>;
using reference = value_type &;
using const_reference = value_type const &;
using iterator = typename container_type::iterator;
private:
container_type m_data_array;
template <typename = void>
static constexpr size_type linearise ()
{ return 0u; }
template <std::size_t First, std::size_t ... Other, typename ... Ts>
static constexpr size_type linearise (std::size_t index,
Ts ... indexes)
{ return multidimensional_array::multiply<Other...>() * index
+ multidimensional_array::linearise<Other...>(indexes...); }
public:
// Constructor
explicit multidimensional_array (const_reference value = value_type{})
{ multidimensional_array::fill(value); }
// Accessors
template <typename ... Ts>
reference operator() (Ts ... indexes)
{ return m_data_array[
multidimensional_array::linearise<Dimensions...>(indexes...)]; }
template <typename ... Ts>
const_reference operator() (Ts ... indexes) const
{ return m_data_array[
multidimensional_array::linearise<Dimensions...>(indexes...)]; }
// Iterators
iterator begin ()
{ return m_data_array.begin(); }
iterator end ()
{ return m_data_array.end(); }
// Other
void fill (const_reference value)
{ m_data_array.fill(value); }
};
int main ()
{
multidimensional_array<int, 2u, 3u, 4u, 5u, 6u> foo;
int k{ 0 };
for ( auto & s : foo )
s = k++;
std::cout << foo(0u, 0u, 0u, 1u, 0u) << std::endl;
}
Bonus suggestion.
You tagged C++17 so you can use "folding".
So you can substitute the couple of multiply() template functions
template <typename = void>
static constexpr size_type multiply ()
{ return 1u; }
template <std::size_t First, std::size_t ... Other>
static constexpr size_type multiply ()
{ return First * multidimensional_array::multiply<Other...>(); }
with a single folded one
template <std::size_t ... Sizes>
static constexpr size_type multiply ()
{ return ( 1U * ... * Sizes ); }
My approach is similar to that in this answer, except that instead of using std::tuple to store a list of types, I define my own type size_t_pack to store a (compile-time) list of size_t's.
using std::size_t;
template<size_t... values>
struct size_t_pack{};
template<size_t first_value,size_t... rest_values>
struct size_t_pack<first_value,rest_values...>{
static constexpr size_t first=first_value;
using rest=size_t_pack<rest_values...>;
static constexpr size_t product=first*rest::product;
};
template<>struct size_t_pack<>{
static constexpr size_t product=1;
};
Defines members: first, rest (in case not empty) and product (since it's not possible to specialize a function using the templates of a template argument, as far as I know, another choice is to if constexpr and make the type support checking for empty)
With that, it's easy to define the linearize function:
template<class dimensions,class... SizeTs>
static constexpr size_type linearise(std::size_t index, SizeTs... indices)
{
using restDimensions=typename dimensions::rest;
return restDimensions::product *index +
multidimensional_array::linearise<restDimensions>(indices...);
}
Using a std::tuple to store the list of types (SizeTs) is also possible, although struct partial specialization is still required, as far as I know.
You need to make indexes a parameter pack by making the operator() function a template, and expand the parameter pack when you use it by putting ... afterwards:
template <class... DimensionType>
const_reference operator()(DimensionType... indexes) const
{
return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes...)];
}
See: parameter pack expansion
The code still will not compile because of a similar problem in linearize(), but that gets you on the right track.
Related
I know that I can use SFINAE to disable generation of templated functions based on a condition, but that doesn't really work in this case. I want to initialize an array at compile-time that should contain values that matches a condition. Something like this:
template <std::size_t i, class ... Types, class ... Group>
constexpr auto fetch_match(const std::tuple<Group...>& candidates)
{
if constexpr (is_match<std::tuple<Group...>, i, Types...>())
{
auto& group = std::get<i>(candidates);
return group.template get<Types...>();
}
}
template <class ... Types, class ... Group, std::size_t ... indices>
constexpr auto get_matches(const std::tuple<Group...>& candidates, std::index_sequence<indices...>)
{
constexpr std::array views {
(fetch_match<indices, Types...>(candidates), ...),
};
return views;
}
I know the code above is wrong and doesn't compile. If the condition isn't filled, then I want the fold expression to not generate that function call. How would I do that?
This question might be an XY-problem, so here's a the problem in more detail.
I have a Registry that contains Groups of heterogeneous data. I want to be able to query all groups that contains the specified sub list of types. For example, for (const auto& view : registry.get<char, short, int>()) should yield an array with views of the groups that contain char, short and int. I've created a mcve below. The problem with the current code is that I have to first create the array and then copy the views, which I'd like to avoid.
#include <tuple>
#include <array>
#include <utility>
#include <type_traits>
#include <iostream>
template <typename T, typename... Ts>
constexpr bool contains = (std::is_same<T, Ts>{} || ...);
template <typename Subset, typename Set>
constexpr bool is_subset_of = false;
template <typename... Ts, typename... Us>
constexpr bool is_subset_of<std::tuple<Ts...>, std::tuple<Us...>> = (contains<Ts, Us...> && ...);
template <typename ... T>
struct View
{
const char* name_of_group; // For debugging.
std::tuple<T...> data;
};
template <typename ... Ts>
struct Group
{
using type_set = std::tuple<Ts...>;
static const char* name; // For debugging.
std::tuple<Ts...> data;
explicit Group(Ts... values) : data(values...) {}
template <typename ... Us>
[[nodiscard]] View<Us...> get() const noexcept
{
return { this->name, std::make_tuple(std::get<Us>(this->data)...) };
}
};
template <class Groups, std::size_t i, class ... Types>
constexpr bool is_match()
{
using group_type = std::tuple_element_t<i, Groups>;
bool match = is_subset_of<std::tuple<Types...>, typename group_type::type_set>;
return match;
}
template <std::size_t i, class ... Types, class ... Group, class Array>
constexpr void add_matches(const std::tuple<Group...>& candidates, Array& matches, std::size_t& index)
{
if constexpr (is_match<std::tuple<Group...>, i, Types...>())
{
auto& group = std::get<i>(candidates);
matches[index++] = group.template get<Types...>();
}
}
template <class ... Types, class ... Group, std::size_t ... indices>
constexpr auto get_matches(const std::tuple<Group...>& candidates, std::index_sequence<indices...>)
{
constexpr std::size_t size = (is_match<std::tuple<Group...>, indices, Types...>() + ... + 0);
std::array<View<Types...>, size> views {};
std::size_t index = 0;
(add_matches<indices, Types...>(candidates, views, index), ...);
return views;
}
template <typename ... Group>
class Registry
{
public:
explicit Registry(Group... groups) : groups(groups...) {}
template <typename ... T>
auto get()
{
constexpr auto indices = std::index_sequence_for<Group...>{};
return get_matches<T...>(this->groups, indices);
}
private:
std::tuple<Group...> groups;
};
using A = Group<char>;
using B = Group<char, short>;
using C = Group<char, short, int>;
using D = Group<char, short, int, long long>;
// Giving the classes names for debugging purposes.
template<> const char* A::name = "A";
template<> const char* B::name = "B";
template<> const char* C::name = "C";
template<> const char* D::name = "D";
int main()
{
auto registry = Registry(A{0}, B{1,1}, C{2,2,2}, D{3,3,3,3});
// Should yield an array of size 2 with View<char, short, int>,
// one from group C and one from Group D.
for (const auto& view : registry.get<char, short, int>())
{
std::cout << "View of group: " << view.name_of_group << std::endl;
std::cout << "char: " << int(std::get<char>(view.data)) << std::endl;
std::cout << "short: " << std::get<short>(view.data) << std::endl;
std::cout << "int: " << std::get<int>(view.data) << std::endl;
}
}
Trying the suggestion in the comments, the following code is as far as I got.
template <class Groups, std::size_t i, class ... Types>
constexpr bool is_match()
{
using group_type = std::tuple_element_t<i, Groups>;
bool match = is_subset_of<std::tuple<Types...>, typename group_type::type_set>;
return match;
}
template <class ... Types, class ... Group, std::size_t ... indices>
constexpr auto build_view_array(const std::tuple<Group...>& candidates, std::index_sequence<indices...>)
{
std::array views {
std::get<indices>(candidates).template get<Types...>()...
};
return views;
}
template <std::size_t i, class Groups, class TypeSet, std::size_t ... x>
constexpr auto get_matching_indices()
{
if constexpr (is_match<Groups, i, TypeSet>())
return std::index_sequence<x..., i>{};
else
return std::index_sequence<x...>{};
}
template <std::size_t i, std::size_t j, std::size_t ... rest, class Groups, class TypeSet, std::size_t ... x>
constexpr auto get_matching_indices()
{
if constexpr (is_match<Groups, i, TypeSet>())
return get_matching_indices<j, rest..., Groups, TypeSet, i, x...>();
else
return get_matching_indices<j, rest..., Groups, TypeSet, x...>();
}
template <class ... Types, class ... Group, std::size_t ... indices>
constexpr auto get_matches(const std::tuple<Group...>& candidates, std::index_sequence<indices...>)
{
constexpr auto matching_indices = get_matching_indices<indices..., std::tuple<Group...>, std::tuple<Types...>>();
constexpr auto views = build_view_array<Types...>(candidates, matching_indices);
return views;
}
It feels like it should work, but it won't compile due to the following error:
/Users/tedkleinbergman/Programming/ECS/temp.cpp:76:39: error: no matching function for call to 'get_matching_indices'
constexpr auto matching_indices = get_matching_indices<indices..., std::tuple<Group...>, std::tuple<Types...>>();
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/tedkleinbergman/Programming/ECS/temp.cpp:92:16: note: in instantiation of function template specialization 'get_matches<char, short, int, Group<char>, Group<char, short>, Group<char, short, int>, Group<char, short, int, long long> , 0, 1, 2, 3>' requested here
return get_matches<T...>(this->groups, indices);
^
/Users/tedkleinbergman/Programming/ECS/temp.cpp:118:38: note: in instantiation of function template specialization 'Registry<Group<char>, Group<char, short>, Group<char, short, int>, Group<char, short, int, long long> >::get<char, short, int>' requested here
for (const auto& view : registry.get<char, short, int>())
^
/Users/tedkleinbergman/Programming/ECS/temp.cpp:57:16: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'Groups'
constexpr auto get_matching_indices()
^
/Users/tedkleinbergman/Programming/ECS/temp.cpp:65:16: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'rest'
constexpr auto get_matching_indices()
^
1 error generated.
First, start with an index_sequence filter:
template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index = {};
template<std::size_t...Is, std::size_t...Js>
constexpr std::index_sequence<Is...,Js...> concatenate( std::index_sequence<Is...>, std::index_sequence<Js...> ) {
return {};
}
template <class Test>
constexpr auto filter_sequence(std::index_sequence<> sequence, Test test) {
return sequence;
}
template<std::size_t I0, std::size_t...Is, class Test>
constexpr auto filter_sequence( std::index_sequence<I0, Is...>, Test test )
{
constexpr auto tail = filter_sequence( std::index_sequence<Is...>{}, test );
if constexpr ( test(index<I0>) ) {
return concatenate( std::index_sequence<I0>{}, tail );
} else {
return tail;
}
}
we then use these primitives.
template <class Group, class ... Types>
constexpr auto get_match_indexes()
{
constexpr auto test = [](auto I){ return is_match<Group, I, Types...>(); };
constexpr auto indexes = std::make_index_sequence< std::tuple_size_v<Group> >{};
constexpr auto retval = filter_sequence( indexes, test );
return retval;
}
template<class ... Types, class Group, std::size_t...Is>
std::array<sizeof...Is, View<Types...>> get_matches(const Group& candidates, std::index_sequence<Is...> ) {
return {{
std::get<Is>(candidates).template get<Types...>(), ...
}};
}
template<class ... Types, class Group>
std::array<sizeof...Is, View<Types...>> get_matches(const Group& candidates ) {
return get_matches<Types...>( candidates, get_match_indexes<Group, Types...>() );
}
or something like that.
Note that some compilers may need to replace is_match<Group, I, Types...>() with is_match<Group, decltype(I)::value, Types...>().
There may be typos. This uses c++17 at the least.
filter_sequence uses O(n^2) template symbol length and O(n) recursive template instantiation depth. It can be improved to O(n lg n) length and O(lg n) depth with a tricky code; basically, you need to split Is... into As... and Bs... down the middle and recurse that way.
Here is a log-depth split of an index sequence:
template<class A, class B>
struct two_things {
A a;
B b;
};
template<class A, class B>
two_things(A,B)->two_things<A,B>;
template<class Seq>
constexpr auto split_sequence( index_t<0>, Seq seq ) {
return two_things{ std::index_sequence<>{}, seq };
}
template<std::size_t I0, std::size_t...Is>
constexpr auto split_sequence( index_t<1>, std::index_sequence<I0, Is...> seq ) {
return two_things{ std::index_sequence<I0>{}, std::index_sequence<Is...>{} };
}
template<std::size_t N, class Seq>
constexpr auto split_sequence( index_t<N>, Seq seq ) {
constexpr auto step1 = split_sequence( constexpr_index<N/2>, seq );
constexpr auto step2 = split_sequence( constexpr_index<N-N/2>, step1.b );
return two_things{ concatenate(step1.a, step2.a), step2.b };
}
template<std::size_t...Is>
constexpr auto halve_sequence( std::index_sequence<Is...> seq ) {
return split( index< (sizeof...(Is)) / 2u >, seq );
}
(two_things exists as a many-many-many times lighter tuple or pair than the std one).
That in turn lets you improve filter sequence.
template<std::size_t I, class Test>
constexpr auto filter_sequence( std::index_sequence<I> seq, Test test )
{
if constexpr ( test(constexpr_index<I>) ) {
return seq;
} else {
return std::index_sequence<>{};
}
}
template<std::size_t...Is, class Test>
constexpr auto filter_sequence( std::index_sequence<Is...> seq, Test test )
{
constexpr auto split = halve_sequence( seq );
constexpr auto head = filter_sequence( split.a, test );
constexpr auto tail = filter_sequence( split.b, test );
return concatenate(head, tail);
}
this version should compile faster and use less memory, especially for large numbers of elements. But you should start with the simpler one above, because (as I noted) there are probably plenty of tpyos.
Live example.
I am trying to write a function in order to generate arbitrarily nested vectors and initialize with the given specific value in C++. For example, auto test_vector = n_dim_vector_generator<2, long double>(static_cast<long double>(1), 1); is expected to create a "test_vector" object which type is std::vector<std::vector<long double>>. The content of this test_vector should as same as the following code.
std::vector<long double> vector1;
vector1.push_back(1);
std::vector<std::vector<long double>> test_vector;
test_vector.push_back(vector1);
The more complex usage of the n_dim_vector_generator function:
auto test_vector2 = n_dim_vector_generator<15, long double>(static_cast<long double>(2), 3);
In this case, the parameter static_cast<long double>(2) is as the data in vectors and the number 3 is as the push times. So, the content of this test_vector2 should as same as the following code.
std::vector<long double> vector1;
vector1.push_back(static_cast<long double>(2));
vector1.push_back(static_cast<long double>(2));
vector1.push_back(static_cast<long double>(2));
std::vector<std::vector<long double>> vector2;
vector2.push_back(vector1);
vector2.push_back(vector1);
vector2.push_back(vector1);
std::vector<std::vector<std::vector<long double>>> vector3;
vector3.push_back(vector2);
vector3.push_back(vector2);
vector3.push_back(vector2);
//...Totally repeat 15 times in order to create test_vector2
std::vector<...std::vector<long double>> test_vector2;
test_vector2.push_back(vector14);
test_vector2.push_back(vector14);
test_vector2.push_back(vector14);
The detail to implement n_dim_vector_generator function is as follows.
#include <iostream>
#include <vector>
template <typename T, std::size_t N>
struct n_dim_vector_type;
template <typename T>
struct n_dim_vector_type<T, 0> {
using type = T;
};
template <typename T, std::size_t N>
struct n_dim_vector_type {
using type = std::vector<typename n_dim_vector_type<T, N - 1>::type>;
};
template<std::size_t N, typename T>
typename n_dim_vector_type<T,N>::type n_dim_vector_generator(T t, unsigned int);
template <std::size_t N, typename T>
typename n_dim_vector_type<T, N>::type n_dim_vector_generator<N, T>(T input_data, unsigned int push_back_times) {
if (N == 0)
{
return std::move(input_data);
}
typename n_dim_vector_type<T, N>::type return_data;
for (size_t loop_number = 0; loop_number < push_back_times; loop_number++)
{
return_data.push_back(n_dim_vector_generator<N - 1, T>(input_data, push_back_times));
}
return return_data;
}
As a result, I got an error 'return': cannot convert from 'long double' to 'std::vector<std::vector<long double,std::allocator<long double>>,std::allocator<std::vector<long double,std::allocator<long double>>>>' I know that it caused by if (N == 0) block which is as the terminate condition to recursive structure. However, if I tried to edit the terminate condition into separate form.
template <typename T>
inline T n_dim_vector_generator<0, T>(T input_data, unsigned int push_back_times) {
return std::move(input_data);
}
template <std::size_t N, typename T>
typename n_dim_vector_type<T, N>::type n_dim_vector_generator<N, T>(T input_data, unsigned int push_back_times) {
typename n_dim_vector_type<T, N>::type return_data;
for (size_t loop_number = 0; loop_number < push_back_times; loop_number++)
{
return_data.push_back(n_dim_vector_generator<N - 1, T>(input_data, push_back_times));
}
return return_data;
}
The error 'n_dim_vector_generator': illegal use of explicit template arguments happened. Is there any better solution to this problem?
The develop environment is in Windows 10 1909 with Microsoft Visual Studio Enterprise 2019 Version 16.4.3
To achieve your specific mapping of:
auto test_vector = n_dim_vector_generator<2, long double>(2, 3)
to a 3x3 vector filled with 2's, your template can be a bit simpler if you take advantage of this vector constructor:
std::vector<std::vector<T>>(COUNT, std::vector<T>(...))
Since vector is copyable, this will fill COUNT slots with a different copy of the vector. So...
template <size_t N, typename T>
struct n_dim_vector_generator {
using type = std::vector<typename n_dim_vector_generator<N-1, T>::type>;
type operator()(T value, size_t size) {
return type(size, n_dim_vector_generator<N-1, T>{}(value, size));
}
};
template <typename T>
struct n_dim_vector_generator<0, T> {
using type = T;
type operator()(T value, size_t size) {
return value;
}
};
usage:
auto test_vector = n_dim_vector_generator<2, long double>{}(2, 3);
Demo: https://godbolt.org/z/eiDAUG
For the record, to address some concerns from the comments, C++ has an idiomatic, initializable, contiguous-memory class equivalent of a multi-dimension C array: a nested std::array:
std::array<std::array<long double, COLUMNS>, ROWS> test_array = { /*...*/ };
for (auto& row : test_array)
for (auto cell : row)
std::cout << cell << std::endl;
If you wanted to reduce the boilerplate to declare one, you can use a struct for that:
template <typename T, size_t... N>
struct multi_array;
template <typename T, size_t NFirst, size_t... N>
struct multi_array<T, NFirst, N...> {
using type = std::array<typename multi_array<T, N...>::type, NFirst>;
};
template <typename T, size_t NLast>
struct multi_array<T, NLast> {
using type = std::array<T, NLast>;
};
template <typename T, size_t... N>
using multi_array_t = typename multi_array<T, N...>::type;
Then to use:
multi_array_t<long double, ROWS, COLUMNS> test_array = { /*...*/ };
for (auto& row : test_array)
for (auto cell : row)
std::cout << cell << std::endl;
This is allocated on the stack, like a C array. That will eat up your stack space for a big array of course. But you can make a decorator range around std::unique_ptr to make a pointer to one a bit easier to access:
template <typename T, size_t... N>
struct dynamic_multi_array : std::unique_ptr<multi_array_t<T, N...>> {
using std::unique_ptr<multi_array_t<T, N...>>::unique_ptr;
constexpr typename multi_array_t<T, N...>::value_type& operator [](size_t index) { return (**this)[index]; }
constexpr const typename multi_array_t<T, N...>::value_type& operator [](size_t index) const { return (**this)[index]; }
constexpr typename multi_array_t<T, N...>::iterator begin() { return (**this).begin(); }
constexpr typename multi_array_t<T, N...>::iterator end() { return (**this).end(); }
constexpr typename multi_array_t<T, N...>::const_iterator begin() const { return (**this).begin(); }
constexpr typename multi_array_t<T, N...>::const_iterator end() const { return (**this).end(); }
constexpr typename multi_array_t<T, N...>::const_iterator cbegin() const { return (**this).cbegin(); }
constexpr typename multi_array_t<T, N...>::const_iterator cend() const { return (**this).cend(); }
constexpr typename multi_array_t<T, N...>::size_type size() const { return (**this).size(); }
constexpr bool empty() const { return (**this).empty(); }
constexpr typename multi_array_t<T, N...>::value_type* data() { return (**this).data(); }
constexpr const typename multi_array_t<T, N...>::value_type* data() const { return (**this).data(); }
};
(let the buyer beware if you use those methods with nullptr)
Then you can still brace-initialize a new expression and use it like a container:
dynamic_multi_array<long double, ROWS, COLUMNS> test_array {
new multi_array_t<long double, ROWS, COLUMNS> { /* ... */ }
};
for (auto& row : test_array)
for (auto cell : row)
std::cout << cell << std::endl;
Demo: https://godbolt.org/z/lUwVE_
I want to write a template function that writes tables to HDF5 files.
The signature should look similar to
template<typename record> void writeTable(const std::vector<record>& data);
where record is a struct, or
template<typename... elements>
void writeTable(const std::vector<std::tuple<elements...>>& data);
The actual implementation would have more parameters to determine the destionation, etc.
To write the data I need to define a HDF5 compound type, which contains the name and the offset of the members. Usually you would use the HOFFSET macro the get the field offset, but as I don't know the struct fields beforehand I can't do that.
What I tried so far was constructing a struct type from the typename pack. The naive implementation did not have standard layout, but the implementation here does. All that's left is get the offsets of the members. I would like to expand the parameter pack into an initializer list with the offsets:
#include <vector>
template<typename... members> struct record {};
template<typename member, typename... members> struct record<member, members...> :
record<members...> {
record(member m, members... ms) : record<members...>(ms...), tail(m) {}
member tail;
};
template<typename... Args> void
make_table(const std::string& name, const std::vector<record<Args...>>& data) {
using record_type = record<Args...>;
std::vector<size_t> offsets = { get_offset(record_type,Args)... };
}
int main() {
std::vector<record<int, float>> table = { {1, 1.0}, {2, 2.0} };
make_table("table", table);
}
Is there a possible implementation for get_offset? I would think not, because in the case of record<int, int> it would be ambiguous. Is there another way to do it?
Or is there any other way I could approach this problem?
Calculating offsets is quite simple. Given a tuple with types T0, T1 ... TN. The offset of T0 is 0 (as long as you use alignas(T0) on your char array. The offset of T1 is the sizeof(T0) rounded up to alignof(T1).
In general, the offset of TB (which comes after TA) is round_up(offset_of<TA>() + sizeof(TA), alignof(TB)).
Calculating the offsets of elements in a std::tuple could be done like this:
constexpr size_t roundup(size_t num, size_t multiple) {
const size_t mod = num % multiple;
return mod == 0 ? num : num + multiple - mod;
}
template <size_t I, typename Tuple>
struct offset_of {
static constexpr size_t value = roundup(
offset_of<I - 1, Tuple>::value + sizeof(std::tuple_element_t<I - 1, Tuple>),
alignof(std::tuple_element_t<I, Tuple>)
);
};
template <typename Tuple>
struct offset_of<0, Tuple> {
static constexpr size_t value = 0;
};
template <size_t I, typename Tuple>
constexpr size_t offset_of_v = offset_of<I, Tuple>::value;
Here's a test suite. As you can see from the first test, the alignment of elements is taken into account.
static_assert(offset_of_v<1, std::tuple<char, long double>> == 16);
static_assert(offset_of_v<2, std::tuple<char, char, long double>> == 16);
static_assert(offset_of_v<3, std::tuple<char, char, char, long double>> == 16);
static_assert(offset_of_v<4, std::tuple<char, char, char, char, long double>> == 16);
static_assert(offset_of_v<0, std::tuple<int, double, int, char, short, long double>> == 0);
static_assert(offset_of_v<1, std::tuple<int, double, int, char, short, long double>> == 8);
static_assert(offset_of_v<2, std::tuple<int, double, int, char, short, long double>> == 16);
static_assert(offset_of_v<3, std::tuple<int, double, int, char, short, long double>> == 20);
static_assert(offset_of_v<4, std::tuple<int, double, int, char, short, long double>> == 22);
static_assert(offset_of_v<5, std::tuple<int, double, int, char, short, long double>> == 32);
I hardcoded the offsets in the above tests. The offsets are correct if the following tests succeed.
static_assert(sizeof(char) == 1 && alignof(char) == 1);
static_assert(sizeof(short) == 2 && alignof(short) == 2);
static_assert(sizeof(int) == 4 && alignof(int) == 4);
static_assert(sizeof(double) == 8 && alignof(double) == 8);
static_assert(sizeof(long double) == 16 && alignof(long double) == 16);
std::tuple seems to store it's elements sequentially (without sorting them to optimize padding). That's proven by the following tests. I don't think the standard requires std::tuple to be implemented this way so I don't think the following tests are guaranteed to succeed.
template <size_t I, typename Tuple>
size_t real_offset(const Tuple &tup) {
const char *base = reinterpret_cast<const char *>(&tup);
return reinterpret_cast<const char *>(&std::get<I>(tup)) - base;
}
int main(int argc, char **argv) {
using Tuple = std::tuple<int, double, int, char, short, long double>;
Tuple tup;
assert((offset_of_v<0, Tuple> == real_offset<0>(tup)));
assert((offset_of_v<1, Tuple> == real_offset<1>(tup)));
assert((offset_of_v<2, Tuple> == real_offset<2>(tup)));
assert((offset_of_v<3, Tuple> == real_offset<3>(tup)));
assert((offset_of_v<4, Tuple> == real_offset<4>(tup)));
assert((offset_of_v<5, Tuple> == real_offset<5>(tup)));
}
Now that I've gone to all of this effort, would that real_offset function suit your needs?
This is a minimal implementation of a tuple that accesses a char[] with offset_of. This is undefined behavior though because of the reinterpret_cast. Even though I'm constructing the object in the same bytes and accessing the object in the same bytes, it's still UB. See this answer for all the standardese. It will work on every compiler you can find but it's UB so just use it anyway. This tuple is standard layout (unlike std::tuple). If the elements of your tuple are all trivially copyable, you can remove the copy and move constructors and replace them with memcpy.
template <typename... Elems>
class tuple;
template <size_t I, typename Tuple>
struct tuple_element;
template <size_t I, typename... Elems>
struct tuple_element<I, tuple<Elems...>> {
using type = std::tuple_element_t<I, std::tuple<Elems...>>;
};
template <size_t I, typename Tuple>
using tuple_element_t = typename tuple_element<I, Tuple>::type;
template <typename Tuple>
struct tuple_size;
template <typename... Elems>
struct tuple_size<tuple<Elems...>> {
static constexpr size_t value = sizeof...(Elems);
};
template <typename Tuple>
constexpr size_t tuple_size_v = tuple_size<Tuple>::value;
constexpr size_t roundup(size_t num, size_t multiple) {
const size_t mod = num % multiple;
return mod == 0 ? num : num + multiple - mod;
}
template <size_t I, typename Tuple>
struct offset_of {
static constexpr size_t value = roundup(
offset_of<I - 1, Tuple>::value + sizeof(tuple_element_t<I - 1, Tuple>),
alignof(tuple_element_t<I, Tuple>)
);
};
template <typename Tuple>
struct offset_of<0, Tuple> {
static constexpr size_t value = 0;
};
template <size_t I, typename Tuple>
constexpr size_t offset_of_v = offset_of<I, Tuple>::value;
template <size_t I, typename Tuple>
auto &get(Tuple &tuple) noexcept {
return *reinterpret_cast<tuple_element_t<I, Tuple> *>(tuple.template addr<I>());
}
template <size_t I, typename Tuple>
const auto &get(const Tuple &tuple) noexcept {
return *reinterpret_cast<tuple_element_t<I, Tuple> *>(tuple.template addr<I>());
}
template <typename... Elems>
class tuple {
alignas(tuple_element_t<0, tuple>) char storage[offset_of_v<sizeof...(Elems), tuple<Elems..., char>>];
using idx_seq = std::make_index_sequence<sizeof...(Elems)>;
template <size_t I>
void *addr() {
return static_cast<void *>(&storage + offset_of_v<I, tuple>);
}
template <size_t I, typename Tuple>
friend auto &get(const Tuple &) noexcept;
template <size_t I, typename Tuple>
friend const auto &get(Tuple &) noexcept;
template <size_t... I>
void default_construct(std::index_sequence<I...>) {
(new (addr<I>()) Elems{}, ...);
}
template <size_t... I>
void destroy(std::index_sequence<I...>) {
(get<I>(*this).~Elems(), ...);
}
template <size_t... I>
void move_construct(tuple &&other, std::index_sequence<I...>) {
(new (addr<I>()) Elems{std::move(get<I>(other))}, ...);
}
template <size_t... I>
void copy_construct(const tuple &other, std::index_sequence<I...>) {
(new (addr<I>()) Elems{get<I>(other)}, ...);
}
template <size_t... I>
void move_assign(tuple &&other, std::index_sequence<I...>) {
(static_cast<void>(get<I>(*this) = std::move(get<I>(other))), ...);
}
template <size_t... I>
void copy_assign(const tuple &other, std::index_sequence<I...>) {
(static_cast<void>(get<I>(*this) = get<I>(other)), ...);
}
public:
tuple() noexcept((std::is_nothrow_default_constructible_v<Elems> && ...)) {
default_construct(idx_seq{});
}
~tuple() {
destroy(idx_seq{});
}
tuple(tuple &&other) noexcept((std::is_nothrow_move_constructible_v<Elems> && ...)) {
move_construct(other, idx_seq{});
}
tuple(const tuple &other) noexcept((std::is_nothrow_copy_constructible_v<Elems> && ...)) {
copy_construct(other, idx_seq{});
}
tuple &operator=(tuple &&other) noexcept((std::is_nothrow_move_assignable_v<Elems> && ...)) {
move_assign(other, idx_seq{});
return *this;
}
tuple &operator=(const tuple &other) noexcept((std::is_nothrow_copy_assignable_v<Elems> && ...)) {
copy_assign(other, idx_seq{});
return *this;
}
};
Alternatively, you could use this function:
template <size_t I, typename Tuple>
size_t member_offset() {
return reinterpret_cast<size_t>(&std::get<I>(*static_cast<Tuple *>(nullptr)));
}
template <typename Member, typename Class>
size_t member_offset(Member (Class::*ptr)) {
return reinterpret_cast<size_t>(&(static_cast<Class *>(nullptr)->*ptr));
}
template <auto MemPtr>
size_t member_offset() {
return member_offset(MemPtr);
}
Once again, this is undefined behavior (because of the nullptr dereference and the reinterpret_cast) but it will work as expected with every major compiler. The function cannot be constexpr (even though member offset is a compile-time calculation).
Not sure to understand what do you exactly want but... what about using recursion based on a index sequence (starting from C++14) something as follows?
#include <vector>
#include <utility>
#include <iostream>
template <typename... members>
struct record
{ };
template <typename member, typename... members>
struct record<member, members...> : record<members...>
{
record (member m, members... ms) : record<members...>(ms...), tail(m)
{ }
member tail;
};
template <std::size_t, typename, std::size_t = 0u>
struct get_offset;
template <std::size_t N, typename A0, typename ... As, std::size_t Off>
struct get_offset<N, record<A0, As...>, Off>
: public get_offset<N-1u, record<As...>, Off+sizeof(A0)>
{ };
template <typename A0, typename ... As, std::size_t Off>
struct get_offset<0u, record<A0, As...>, Off>
: public std::integral_constant<std::size_t, Off>
{ };
template <typename... Args, std::size_t ... Is>
auto make_table_helper (std::string const & name,
std::vector<record<Args...>> const & data,
std::index_sequence<Is...> const &)
{ return std::vector<std::size_t>{ get_offset<Is, record<Args...>>::value... }; }
template <typename... Args>
auto make_table (std::string const & name,
std::vector<record<Args...>> const & data)
{ return make_table_helper(name, data, std::index_sequence_for<Args...>{}); }
int main ()
{
std::vector<record<int, float>> table = { {1, 1.0}, {2, 2.0} };
auto v = make_table("table", table);
for ( auto const & o : v )
std::cout << o << ' ';
std::cout << std::endl;
}
Unfortunately isn't an efficient solution because the last value is calculated n-times.
I am trying to develop a constexpr, functional list data structure in C++. I am also trying to take advantage of recursive structure of the cons list approach. I had couple of attempts and for those, you can see my past questions. For now, I have settled on to the idea of making the size a part of type, ie. `List. Here is the code:
#include <iostream>
#include <type_traits>
template <typename T, unsigned int N>
struct list {
public:
constexpr list(const T& head, const list<T, N-1> &tail)
:_length{N}, _head{head}, _tail{tail}
{}
const unsigned int _length;
const T _head;
const list<T, N-1> _tail;
};
template <typename T, unsigned int N>
constexpr auto head(const list<T, N> &li) {
return li._head;
}
template <typename T, unsigned int N>
constexpr auto tail(const list<T, N> &li) {
return li._tail;
}
template <typename T, unsigned int N>
constexpr auto prepend(const T &new_head, const list<T, N> &li){
return list<T, N + 1>{new_head,
list<T, N>{head(li), tail(li)}};
}
template <typename T>
struct list<T, 0>
{
constexpr static const bool nil = true;
};
template <typename T>
using nil = list<T, 0>;
template <typename Functor, typename T, unsigned int N>
constexpr auto fmap(Functor f, const list<T, N> &li) {
if constexpr(N == 0)
return nil<T>{};
else
return list{f(head(li)), fmap(f, tail(li))};
}
template <typename T, unsigned int N>
std::ostream& operator<<(std::ostream& str, const list<T, N> &li) {
if constexpr(N == 0)
return str;
else{
str << head(li) << ' ' << tail(li);
return str;
}
}
int main(){
constexpr auto f = [](int x){ return x * x; };
constexpr list<char, 2> p2{'U', list<char, 1>{'S', nil<char>{}}};
constexpr auto p3 = prepend('S', p2);
constexpr list<int, 2> i1{1, list<int, 1>{2, nil<int>{}}};
constexpr auto i2 = fmap(f, i1);
std::cout << p3;
}
Some of it works to some extent. fmap is the one keeping my program from compiling. I get the error
prog.cc:47:16: error: no viable constructor or deduction guide for deduction of template arguments of 'list'
return list{f(head(li)), fmap(f, tail(li))};
prog.cc:47:34: note: in instantiation of function template specialization 'fmap<(lambda at prog.cc:61:24), int, 1>' requested here
return list{f(head(li)), fmap(f, tail(li))};
prog.cc:67:25: note: in instantiation of function template specialization 'fmap<(lambda at prog.cc:61:24), int, 2>' requested here
constexpr auto i2 = fmap(f, i1);
prog.cc:8:15: note: candidate template ignored: couldn't infer template argument 'N'
constexpr list(const T& head, const list &tail)
and similar. I am lost here, what is the cause of this error? It seems like compiler deduced the argument N correctly, as it says N=2. I feel like I need to add some base cases related to lists of size zero but could not figure it out.
You have a typo: missing type specifiers when using template struct list. Bellow should work
template <typename Functor, typename T, unsigned int N>
constexpr auto fmap(Functor f, const list<T, N> &li) {
if constexpr(N == 0)
return nil<T>{};
else
return list<T, N-1>{f(head(li)), fmap(f, tail(li))};
}
I had nested partially specialized template code working with VS 2015 until I discovered that it was not standards-compliant. I want it to be so I twisted my code to overcome the former issue and also that one and have now hit a hard wall.
Using variadic templates and partial specialization I would like to fill an array at compile-time given a fixed set of parameters.
What I want to achieve also seems similar to this answer but I did not manage to make it work.
Consider the following program:
#include <cstdlib>
template <typename T, std::size_t Size>
struct Array;
template <typename T, std::size_t Size, std::size_t Iteration, typename ...Args>
struct ArrayFiller {
inline
static void fill(Array<T, Size>& a, const Args&... args) {
ArrayFiller<T, Size, Iteration, Args...>::fill_recursive(a, args...);
}
inline
static void fill_recursive(Array<T, Size>& a, const T& i, const Args&... args) {
a.data[Size - Iteration - 1] = i;
ArrayFiller<T, Size, Iteration - 1>::fill_recursive(a, args...);
}
};
template <typename T, std::size_t Size>
struct ArrayFiller<T, Size, 0> {
inline
static void fill_recursive(Array<T, Size>& a, const T& i) {
a.data[Size - 1] = i;
}
};
template <typename T, std::size_t Size>
struct Array {
T data[Size];
template <typename ...Args>
Array(const Args&... args) {
ArrayFiller<T, Size, Size - 1, Args...>::fill(*this, args...);
}
};
int main() {
Array<int, 2> c(42, -18);
return 0;
}
...and the beginning of its g++ -std=c++14 -pedantic -Wall -Wextra output (as of version 5.3.0):
main.cpp: In instantiation of ‘static void ArrayFiller<T, Size, Iteration, Args>::fill(Array<T, Size>&, const Args& ...) [with T = int; long unsigned int Size = 2ul; long unsigned int Iteration = 1ul; Args = {int, int}]’:
main.cpp:34:54: required from ‘Array<T, Size>::Array(const Args& ...) [with Args = {int, int}; T = int; long unsigned int Size = 2ul]’
main.cpp:39:28: required from here
main.cpp:10:65: error: no matching function for call to ‘ArrayFiller<int, 2ul, 1ul, int, int>::fill_recursive(Array<int, 2ul>&, const int&, const int&)’
ArrayFiller<T, Size, Iteration, Args...>::fill_recursive(a, args...);
^
main.cpp:14:17: note: candidate: static void ArrayFiller<T, Size, Iteration, Args>::fill_recursive(Array<T, Size>&, const T&, const Args& ...) [with T = int; long unsigned int Size = 2ul; long unsigned int Iteration = 1ul; Args = {int, int}]
static void fill_recursive(Array<T, Size>& a, const T& i, const Args&... args) {
^
main.cpp:14:17: note: candidate expects 4 arguments, 3 provided
Basically the compiler complains that there is no matching function because from what I understand the parameter pack is expanded either too "soon" or too "late" in my logic: the const T& i argument in the recursive call messes up the expansion.
How would you fix it?
I am also interested in alternate / better / cleaner solutions.
Is a solution not based on template recursion acceptable in your use case? wandbox link
template <typename T, std::size_t Size>
struct Array {
T data[Size];
template <typename ...Args>
constexpr Array(const Args&... args) : data{args...} {
}
};
int main() {
Array<int, 2> c(42, -18);
assert(c.data[0] == 42);
assert(c.data[1] == -18);
constexpr Array<int, 2> cc(42, -18);
static_assert(cc.data[0] == 42);
static_assert(cc.data[1] == -18);
}
I might be off target here but based on this requirement "... I would like to fill an array at compile-time given a fixed set of parameters." and this code:
int main() {
Array<int, 2> c(42, -18);
return 0;
}
I have been left wondering is this not solvable with a normal array declaration and initialization?
int main() {
constexpr int c []{42, -18};
static_assert( c[0] == 42 ) ;
// and so on
return 0;
}
In a comment to the previous answer, you are mentioning some setter? There must be something missing in here... In case you need to have this class Array as above, perhaps the simplest way to do so is this:
template<typename T, T ... V >
struct Array
{
constexpr static T data_[]{ V... };
// not strictly necessary
constexpr static size_t size{ sizeof(data_) / sizeof(T) };
};
Usage is this:
// length is not required for declaration
using int_array_of_4 = Array<int,1,2,3,4> ;
static_assert( int_array_of_4::data_[0] == 1) ;
// and so on
But I might be barking on the wrong tree here?