C++ function design - c++

I came across the following question in multiple occasions this year but I don't have a clear answer to it: say I want to design a function that accumulates or in general builds something. I would need to declare the initial accumulator value (or an empty object in general), the problem is that whether I should initialize this value or object inside the function arguments with default value or should I initialize this thing inside the function body?
An example would be the following piece of function that split an sequential container into N equal size pieces (precondition: the pieces are splittable).
Is it a okay to write it in the following form
template <typename T, std::size_t N>
array<T, N> equal_split(const T& x, array<T, N> result = {}) {
for (int i = 0; i < N; ++i)
std::copy(begin(x) + i * size(x) / 3, begin(x) + (i + 1) * size(x) / 3, std::back_inserter(result[i]));
return result;
}
or is it better to write it as
template <typename T, std::size_t N>
array<T, N> equal_split(const T& x) {
array<T, N> result = {};
for (int i = 0; i < N; ++i)
std::copy(begin(x) + i * size(x) / 3, begin(x) + (i + 1) * size(x) / 3, std::back_inserter(result[i]));
return result;
}

I would need to declare the initial accumulator value
If it is just an implementation detail, then hide it from interface.
if different initial values make sense, then you might add it in interface.
In your example, signature would be:
template <std::size_t N, typename Container>
array<Container, N> equal_split(const Container&);
Rename T to more meaningful Container
size_t N first, to not have to provide deducible Container
No default parameters, as initial value was just implementation detail.

Related

Pointer level return type based on template function arguments

For a tensor class I would like to have a template creating functions like
double* mat(int size1);
double** mat(int size1, int size2);
double*** mat(int size1, int size2, int size3);
i.e. the pointer level of the return type depends on the number of inputs.
The base case would just be
double* mat(int size1){
return new double[size1];
}
I thought a variadic template could somehow do the trick
template<typename T, typename... size_args>
T* mat(int size1, size_args... sizes) {
T* m = new T[size1];
for(int j = 0; j < size1; j++){
m[j] = mat(sizes...);
}
return m;
}
Deducing the template parameter in m[j] = mat(sizes...); doesn't seem to work though when more than one recursive call would be necessary, as in
auto p = mat<double**>(2,3,4);
but how could I provide the parameter in the recursive call? The correct parameter would be one pointer level below T, that is mat<double**> for T=double***.
I feel like I miss something important about templates here.
you cannot declare m and return type as T* since it is not in multiple dimension.
template<typename T, typename size_type>
auto mat(size_type size){return new T[size];}
template<typename T, typename size_type, typename... size_types>
auto mat(size_type size, size_types... sizes){
using inner_type = decltype(mat<T>(sizes...));
inner_type* m = new inner_type[size];
for(int j = 0; j < size; j++){
m[j] = mat<T>(sizes...);
}
return m;
}
For lack of a better name, I'm going to call the template meta-function that creates a pointer type with the desired "depth" and type, a "recursive pointer". Such a thing can be implemented like so:
template <size_t N, class T>
struct RecursivePtr
{
using type = RecursivePtr<N-1, T>::type*;
};
template <class T>
struct RecursivePtr<0, T>
{
using type = T;
};
template <size_t N, class T>
using recursive_ptr_t = RecursivePtr<N, T>::type;
recursive_ptr_t<4, int> for example creates int****. So in your case you can go ahead and implement the mat function as:
template <class... Args>
auto mat(Args... args)
{
recursive_ptr_t<sizeof...(Args), double> m;
// Runtime allocate your Npointer here.
return m;
}
Demo
Some ideas to give added type safety to the mat function are:
Add static asserts that all provided types are sizes
Statically assert that at least one size has been provided
Minor note, when I say runtime allocate your Npointer above, I mean something like:
template <class T, class... Sz>
void alloc_ar(T* &ar, size_t s1, Sz... ss)
{
if constexpr (sizeof...(Sz))
{
ar = new T[s1];
for (size_t i(0); i < s1; i++)
alloc_ar(ar[i], ss...);
}
else
{
ar = new T[s1];
}
}
Made a Demo where I show the allocation, but not the deallocation.
A reasonable alternative to this is to allocate one contiguous chunk of memory (sized == multiplicate of dimensions) and use the multidimensional pointer to the beginning of that chunk for syntactic sugar when accessing it. This also provides an easier way to deallocate memory.
A second alternative is to use nested vector of vectors (of vectors of vectors...) with the same generation mechanics as the Npointer. This eliminates the need for manual memory management and would probably force you to wrap the whole thing in a more expressive class:
template <class... Dims>
class mat
{
template <size_t N, class T>
struct RecursivePtr
{
using type = std::vector<RecursivePtr<N-1, T>::type>;
};
template <class T>
struct RecursivePtr<0, T>
{
using type = T;
};
// This is the replacement to double***
// translates to vector<vector<vector<double>>>
RecursivePtr<N, T>::type _data;
public:
// construction, argument deduction etc ...
};

C++ constexpr function to checksum an array

Is there away to achieve the behavior shown in the example (non compiling) code below? I (think I) understand why it doesn't compile given that the required calls to std::initializer_list functions are not constexpr.
The goal is to be able to create an array from a constant initialiser list when an additional element is appended to the end that is the sum of the preceeding elements.
All the posts I found on initialising arrays at compile time required lots of complex recursive template function calls and all were related to generating sequences of numbers.
#include <initializer_list>
#include <array>
template <typename T> constexpr auto CheckSummedArray(const std::initializer_list<T> & i)
{
std::array<T, i.size() + 1> res;
std::copy(i.begin(), i.end(), res.begin());
auto cs = T();
for (auto r : i)
{
cs += r;
}
res[res.size() - 1] = cs;
return res;
}
constexpr auto testArray = CheckSummedArray<int>({1,2,3,4});
static_assert(testArray.size() == 5);
static_assert(testArray[0] == 1);
static_assert(testArray[4] == 9);
The issue is not the calls to the members of std::initializer_list, those functions are actually constexpr. The issue is that you are using the result of i.size() in a template parameter, but i is not a constant expression, since it's a function parameter.
You can solve this by making the argument an array type, and so you can deduce its size with a template parameter:
template <typename T, std::size_t N>
constexpr auto CheckSummedArray(T const(&i)[N])
{
std::array<T, N + 1> res{};
std::copy(i, i + N, res.begin());
res.back() = std::accumulate(i, i + N, 0);
return res;
}
Here's a demo.
I cleaned up the function a little by using an algorithm instead of a loop. If you're not using C++20, you don't have access to constexpr algorithms, so your copy and accumulate will need to be raw loops anyway.
for(std::size_t j = 0; j < N; ++j)
{
res[j] = i[j];
res.back() += i[j];
}
Here's a C++17 demo.

Pass N-D array by reference to variadic function

I'd like to make the function multi_dimensional accept a multidimensional array by reference.
Can this be done with a variation of the syntax below which works for three_dimensional?
#include <utility>
// this works, but number of dimensions must be known (not variadic)
template <size_t x, size_t y, size_t z>
void three_dimensional(int (&nd_array)[x][y][z]) {}
// error: parameter packs not expanded with ‘...’
template <size_t... dims>
void multi_dimensional(int (&nd_array)[dims]...) {}
int main() {
int array[2][3][2] = {
{ {0,1}, {2,3}, {4,5} },
{ {6,7}, {8,9}, {10,11} }
};
three_dimensional(array); // OK
// multi_dimensional(array); // error: no matching function
return 0;
}
The main problem is that you cannot make the number of array dimensions itself variadic. So whichever way you go, you will almost certainly need a recursive approach of some sort to deal with the individual array layers. What exactly such approach should look like will mainly depend on what exactly you're planning to do with the array once it's been given to you.
If really all you want is a function that can be given any multi-dimensional array, then just write a function that can be given anything but only exists as long as that anything is an array:
template <typename T>
std::enable_if_t<std::is_array_v<T>> multi_dimensional(T& a)
{
constexpr int dimensions = std::rank_v<T>;
// ...
}
However, this by itself will most likely not get you very far. To actually do anything meaningful with the array you've been given, you will most likely need some recursive walking through subarrays. Unless you really just want to look at the topmost layer of the structure.
Another approach is to use a recursive template to peel back the individual array levels, for example:
// we've reached the bottom
template <typename T, int N>
void multi_dimensional(T (&a)[N])
{
// ...
}
// this matches any array with more than one dimension
template <typename T, int N, int M>
void multi_dimensional(T (&a)[N][M])
{
// peel off one dimension, invoke function for each element on next layer
for (int i = 0; i < N; ++i)
multi_dimensional(a[i]);
}
I would, however, suggest to at least consider using std::array<> instead of raw arrays as the syntax and special behavior of raw arrays tends to turn everything into a confusing mess in no time. In general, it might be worth to implement your own multi-dimensional array type, like an NDArray<int, 2, 3, 2> which internally works with a flattened representation and just maps multi-dimensional indices to a linear index. One advantage of this approach (besides the cleaner syntax) would be that you can easily change the mapping, e.g., to switch from row-major to column-major layout, e.g., for performance optimization…
To implement a general nD array with static dimensions, I would introduce a helper class to encapsulate the recursive computation of a linear index from an nD index:
template <std::size_t... D>
struct row_major;
template <std::size_t D_n>
struct row_major<D_n>
{
static constexpr std::size_t SIZE = D_n;
std::size_t operator ()(std::size_t i_n) const
{
return i_n;
}
};
template <std::size_t D_1, std::size_t... D_n>
struct row_major<D_1, D_n...> : private row_major<D_n...>
{
static constexpr std::size_t SIZE = D_1 * row_major<D_n...>::SIZE;
template <typename... Tail>
std::size_t operator ()(std::size_t i_1, Tail&&... tail) const
{
return i_1 + D_1 * row_major<D_n...>::operator ()(std::forward<Tail>(tail)...);
}
};
And then:
template <typename T, std::size_t... D>
class NDArray
{
using memory_layout_t = row_major<D...>;
T data[memory_layout_t::SIZE];
public:
template <typename... Args>
T& operator ()(Args&&... args)
{
memory_layout_t memory_layout;
return data[memory_layout(std::forward<Args>(args)...)];
}
};
NDArray<int, 2, 3, 5> arr;
int main()
{
int x = arr(1, 2, 3);
}

c++17 efficiently multiply parameter pack arguments with std::array elements

I want to efficiently multiply the arguments from a parameter pack with the elements of a std::array:
int index(auto... Is, std::array<int,sizeof...(Is)> strides)
{
// pseudo-code
// int idx = 0;
// for(int i = 0; i < sizeof...(Is); ++i)
// idx += Is[i] * strides[i];
// return idx;
}
I can't quite wrap my brain around this one. I started down the road of an index sequence, but I could figure out how to incorporate the summation.
I am using c++17, so fold expressions are fair game if they would simplify the code.
Thanks for any pointers.
EDIT: Clarified the pseudo-code. The only pseudo part is the expression Is[i] which refers to the i'th parameter pack argument.
T.C.'s answer below was perfect and here is my final code which is a member function:
unsigned int index(auto... indexes)
{
unsigned int idx = 0, i = 0;
(..., (idx += indexes * m_strides[i++]));
return idx;
}
As of this writing, the code compiles using gcc 6.3.0 with the -fconcepts flag, which brings in the Concept TS.
Using auto... indexes is shorthand for template<typename Args> f(Args... indexes). I tried to use an unsigned int concept for the arguments, but I couldn't get that to work.
The (...,) fold is the key element and expands to something like (if you could actually [] into the parameter pack):
idx += indexes[0] * m_strides[i++], idx += indexes[1] * m_strides[i++], etc.
That was the insight I was missing.
I can't get auto... to work, so I changed the signature of index.
You will need an auxiliary function (index_helper here) to use index_sequence, since it relies on template argument deduction to fill in the indices.
#include <array>
#include <cstdio>
template <typename... T, size_t... i>
// ^~~~~~~~~~~
// use deduction to make {i...} = {0, 1, 2, ..., n}
static int index_helper(const std::array<int, sizeof...(T)>& strides,
std::index_sequence<i...>,
T... Is)
{
return (0 + ... + (strides[i] * Is));
}
template <typename... T>
int index(const std::array<int, sizeof...(T)>& strides, T... Is) {
return index_helper(strides, std::make_index_sequence<sizeof...(T)>(), Is...);
// ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// generates {0, 1, 2, ..., n}
}
int main() {
printf("%d\n", index({1, 100, 100000, 1000}, 2, 3, 5, 7));
// 507302
}
If you can hammer down the argument pack into one single type that is cheap to copy/move, you can just make it into an array:
T arr[] = { static_cast<T>(Is)... }; // for some T, possibly common_type_t<decltype(Is)...>
Then you can just turn your pseudocode into real code.
If that's not feasible, a comma fold can be used:
int idx = 0, i = 0;
(..., (idx += Is * strides[i++]));
return idx;

C++ template meta-programming member function loop unrolling

I have just started to use template meta-programming in my code. I have a class which has as a member which is a vector of a multi-dimensional Cartesian points. Here is a basic setup of the class:
template<size_t N>
class TGrid{
public:
void round_points_3(){
for(std::size_t i = 0; i < Xp.size();i++){
Xp[i][0] = min[0] + (std::floor((Xp[i][0] - min[0]) * nbins[0] / (max[0] - min[0])) * bin_w[0]) + bin_w[0]/2.0;
Xp[i][1] = min[1] + (std::floor((Xp[i][1] - min[1]) * nbins[1] / (max[1] - min[1])) * bin_w[1]) + bin_w[1]/2.0;
Xp[i][2] = min[2] + (std::floor((Xp[i][2] - min[2]) * nbins[2] / (max[2] - min[2])) * bin_w[2]) + bin_w[2]/2.0;
}
}
void round_points_2(){
for(std::size_t i = 0; i < Xp.size();i++){
Xp[i][0] = min[0] + (std::floor((Xp[i][0] - min[0]) * nbins[0] / (max[0] - min[0])) * bin_w[0]) + bin_w[0]/2.0;
Xp[i][1] = min[1] + (std::floor((Xp[i][1] - min[1]) * nbins[1] / (max[1] - min[1])) * bin_w[1]) + bin_w[1]/2.0;
}
}
void round_points_1(){
for(std::size_t i = 0; i < Xp.size();i++){
Xp[i][0] = min[0] + (std::floor((Xp[i][0] - min[0]) * nbins[0] / (max[0] - min[0])) * bin_w[0]) + bin_w[0]/2.0;
}
}
public:
std::vector<std::array<double,N> > Xp;
std::vector<double> min, max, nbins, bin_w;
};
This class represented a multidimensional Grid. The dimension is specified by the template value N. I will be having many operations which can be made more efficient by having template specific member functions tailored to the specific dimensions, such as loop unrolling.
In the class TGrid, I have 3 functions specific for dimensions D=1,D=2 and D=3. This is indicated by the subscript _1,_2 and _3 of the functions.
I am looking for a template meta-programming oriented approach to write
these three functions more compactly.
I have seen examples of loop unrolling but all of these examples don't consider member functions of a template class.
Putting to one side the question of whether or not this is an appropriate optimisation, or if other optimisations should be regarded first, this is how I would do it. (But I do agree, sometimes it is demonstrably better to explicitly unroll loops — the compiler isn't always the best judge.)
One can't partially specialize a member function, and one can't specialize a nested struct without specializing the outer struct, so the only solution is to use a separate templated struct for the unrolling mechanism. Feel free to put this in some other namespace :)
The unrolling implementation:
template <int N>
struct sequence {
template <typename F,typename... Args>
static void run(F&& f,Args&&... args) {
sequence<N-1>::run(std::forward<F>(f),std::forward<Args>(args)...);
f(args...,N-1);
}
};
template <>
struct sequence<0> {
template <typename F,typename... Args>
static void run(F&& f,Args&&... args) {}
};
This takes an arbitrary functional object and a list of arguments, and then calls the object with the arguments and an additional final argument N times, where the final argument ranges from 0 to N-1. The universal references and variadic templates are not necessary; the same idea can be employed in C++98 with less generality.
round_points<K> then calls sequence::run<K> with a helper static member function:
template <size_t N>
class TGrid {
public:
template <size_t K>
void round_points(){
for (std::size_t i = 0; i < Xp.size();i++) {
sequence<K>::run(TGrid<N>::round_item,*this,i);
}
}
static void round_item(TGrid &G,int i,int j) {
G.Xp[i][j] = G.min[j] + (std::floor((G.Xp[i][j] - G.min[j]) * G.nbins[j] / (G.max[j] - G.min[j])) * G.bin_w[j]) + G.bin_w[j]/2.0;
}
// ...
};
Edit: Addendum
Doing the equivalent with a pointer-to-member function appears to be hard for compilers to inline. As an alternative, to avoid the use of a static round_item, you can use a lambda, e.g.:
template <size_t N>
class TGrid {
public:
template <size_t K>
void round_points(){
for (std::size_t i = 0; i < Xp.size();i++) {
sequence<K>::run([&](int j) {round_item(i,j);});
}
}
void round_item(int i,int j) {
Xp[i][j] = min[j] + (std::floor((Xp[i][j] - min[j]) * nbins[j] / (max[j] - min[j])) * bin_w[j]) + bin_w[j]/2.0;
}
// ...
};