std::iota is very limited - c++

Coming from a Python world, I find the function std::iota very limited. Why is the interface restricted to not take any UnaryFunction ?
For instance I can convert
>>> x = range(0, 10)
into
std::vector<int> x(10);
std::iota(std::begin(x), std::end(x), 0);
But how would one do:
>>> x = range(0,20,2)
or even
>>> x = range(10,0,-1)
I know this is trivial to write one such function or use Boost, but I figured that C++ committee must have picked this design with care. So clearly I am missing something from C++11.

how about std::generate?
int n = -2;
std::generate(x.begin(), x.end(), [&n]{ return n+=2; });
int n = 10;
std::generate(x.begin(), x.end(), [&n]{ return n--;});

But how would one do:
x = range(0,20,2)
Alternatively to std::generate() (see other answer), you can provide your own unary function to std::iota(), it just have to be called operator++():
#include <iostream>
#include <functional>
#include <numeric>
#include <vector>
template<class T>
struct IotaWrapper
{
typedef T type;
typedef std::function<type(const type&)> IncrFunction;
type value;
IncrFunction incrFunction;
IotaWrapper() = delete;
IotaWrapper(const type& n, const IncrFunction& incrFunction) : value(n), incrFunction(incrFunction) {};
operator type() { return value; }
IotaWrapper& operator++() { value = incrFunction(value); return *this; }
};
int main()
{
IotaWrapper<int> n(0, [](const int& n){ return n+2; });
std::vector<int> v(10);
std::iota(v.begin(), v.end(), n);
for (auto i : v)
std::cout << i << ' ';
std::cout << std::endl;
}
Output: 0 2 4 6 8 10 12 14 16 18
Demo
Here is an idea of how one could implement Range():
struct Range
{
template<class Value, class Incr>
std::vector<Value> operator()(const Value& first, const Value& last, const Incr& increment)
{
IotaWrapper<Value> iota(first, [=](const int& n){ return n+increment; });
std::vector<Value> result((last - first) / increment);
std::iota(result.begin(), result.end(), iota);
return result;
}
};
Demo

With C++20 ranges, you can write it like this:
static auto stepped_iota(int start, int step) {
return std::ranges::views::iota(0) |
std::ranges::views::transform([=](int x) { return x * step + start; });
}
void f() {
for (int x : stepped_iota(0, 2)) { ... }
}
https://godbolt.org/z/3G49rs
Or, if you want the range to be finite:
static auto stepped_iota(int start, int end, int step) {
return std::ranges::views::iota(0, (end - start + step - 1) / step) |
std::ranges::views::transform([=](int x) { return x * step + start; });
}

Related

C++: function that works with container and container of pointers as well

I think I'm facing something that I imagine is a quite common problem here.
I'd like to write a function that would be able to accept both a container (let's say std::vector) of objects, and a container of pointers to those objects.
What would be the proper way to do so?
Right now, I'm thinking
int sum(std::vector<int *> v)
{
int s = 0;
for (int * i : v) s += *i;
return s;
}
int sum(std::vector<int> v)
{
std::vector<int *> vp;
for (size_t i = 0; i < v.size(); ++i)
vp[i] = &v[i];
return sum(vp);
}
But it doesn't seem quite right, does it?
Consider the standard algorithm library where the problem you see has a solution.
Most algorithms have some default behavior but often allow you to customize that behavior via functor parameters.
For your specific case the algorithm of choice is std::accumulate.
Because this algorithm already exists I can restrict to a rather simplified illustration here:
#include <iostream>
#include <functional>
template <typename T,typename R,typename F = std::plus<>>
R sum(const std::vector<T>& v,R init,F f = std::plus<>{})
{
for (auto& e : v) init = f(init,e);
return init;
}
int main() {
std::vector<int> x{1,2,3,4};
std::vector<int*> y;
for (auto& e : x ) y.push_back(&e);
std::cout << sum(x,0) << "\n";
std::cout << sum(y,0,[](auto a, auto b) {return a + *b;});
}
std::plus is a functor that adds two values. Because the return type may differ from the vectors element type an additional template parameter R is used. Similar to std::accumulate this is deduced from the initial value passed as parameter. When adding int the default std::plus<> is fine. When adding integers pointed to by pointers, the functor can add the accumulator with the dereferenced vector element. As already mentioned this is just a simple toy example. In the above link you can find a possible implementation of std::accumulate (which uses iterators rather than the container directly).
With C++20 (or another ranges library), you can easily add or remove pointerness
template <std::ranges::range R, typename T>
concept range_of = requires std::same<std::ranges::range_value_t<R>, T>;
template <range_of<int *> IntPointers>
int sum_pointers(IntPointers int_pointers)
{
int result = 0;
for (int * p : int_pointers) result += *p;
return result;
}
void call_adding_pointer()
{
std::vector<int> v;
sum_pointers(v | std::ranges::views::transform([](int & i){ return &i; });
}
Or
template <range_of<int> Ints>
int sum(Ints ints)
{
int result = 0;
for (int i : ints) result += i;
return result;
}
void call_removing_pointer()
{
std::vector<int *> v;
sum(v | std::ranges::views::transform([](int * p){ return *p; });
}
You can make a function template, which behaves differently for pointer and non-pointer:
#include <iostream>
#include <vector>
using namespace std;
template <class T>
auto sum(const std::vector<T> &vec)
{
if constexpr (std::is_pointer_v<T>)
{
typename std::remove_pointer<T>::type sum = 0;
for (const auto & value : vec) sum += *value;
return sum;
}
if constexpr (!std::is_pointer_v<T>)
{
T sum = 0;
for (const auto & value : vec) sum += value;
return sum;
}
}
int main(){
std::vector<int> a{3, 4, 5, 8, 10};
std::vector<int*> b{&a[0], &a[1], &a[2], &a[3], &a[4]};
cout << sum(a) << endl;
cout << sum(b) << endl;
}
https://godbolt.org/z/sch3KovaK
You can move almost everything out of the if constexpr to reduce code duplication:
template <class T>
auto sum(const std::vector<T> &vec)
{
typename std::remove_pointer<T>::type sum = 0;
for (const auto & value : vec)
{
if constexpr (std::is_pointer_v<T>)
sum += *value;
if constexpr (!std::is_pointer_v<T>)
sum += value;
}
return sum;
}
https://godbolt.org/z/rvqK89sEK
Based on #mch solution:
template<typename T>
std::array<double, 3> center(const std::vector<T> & particles)
{
if (particles.empty())
return {0, 0, 0};
std::array<double, 3> cumsum = {0, 0, 0};
if constexpr (std::is_pointer_v<T>)
{
for (const auto p : particles)
{
cumsum[0] += p->getX();
cumsum[1] += p->getY();
cumsum[2] += p->getZ();
}
}
if constexpr (not std::is_pointer_v<T>)
{
for (const auto p : particles)
{
cumsum[0] += p.getX();
cumsum[1] += p.getY();
cumsum[2] += p.getZ();
}
}
double f = 1.0 / particles.size();
cumsum[0] *= f;
cumsum[1] *= f;
cumsum[2] *= f;
return cumsum;
}
Much cleaner and more efficient solution using std::invoke:
std::array<double, 3> centroid(const std::vector<T> & particles)
{
if (particles.empty())
return {0, 0, 0};
std::array<double, 3> cumsum{0.0, 0.0, 0.0};
for (auto && p : particles)
{
cumsum[0] += std::invoke(&topology::Particle::getX, p);
cumsum[1] += std::invoke(&topology::Particle::getY, p);
cumsum[2] += std::invoke(&topology::Particle::getZ, p);
}
double f = 1.0 / particles.size();
cumsum[0] *= f;
cumsum[1] *= f;
cumsum[2] *= f;
return cumsum;
}

Is there any equivalent of Python range() in C++?

I want to use std::for_each to iterate over vector indexes in range [a, b) in parallel, calculate the value of the Weierstrass function and write it to the std::vector:
std::vector<std::array<float, 2>> values(1000);
auto range = /** equivalent of Pyhthon range(0, values.size()) **/;
std::for_each(std::execution::par, range.begin(), range.end(), [&](auto &&i) {
values[i][0] = static_cast<float>(i) / resolution;
values[i][1] = weierstrass(a, b, static_cast<float>(i) / resolution);
});
// a, b, and resolution are some constants defined before
// weierstrass() is the Weierstrass function
I have found some solutions in the internet, but all of them requires to include some third-party libraries or create my own range class. Is there any standard solution for this?
You can use std::views::iota(), its use is similar (but a bit different) to Python's range(). With help of std::ranges::for_each(). Both are available in C++20.
Try it online!
#include <algorithm>
#include <ranges>
#include <iostream>
int main() {
std::ranges::for_each(std::views::iota(1, 10), [](int i) {
std::cout << i << ' ';
});
}
Output:
1 2 3 4 5 6 7 8 9
As noted by #Afshin, in code mentioned above std::ranges::for_each() doesn't support std::execution::par for multi-threaded execution.
To overcome this issue you may use iota with regular std::for_each() as following:
Try it online!
#include <algorithm>
#include <ranges>
#include <iostream>
#include <execution>
int main() {
auto range = std::views::iota(1, 10);
std::for_each(std::execution::par, range.begin(), range.end(),
[](int i) {
std::cout << i << ' ';
});
}
Output:
1 2 3 4 5 6 7 8 9
I decided to implement Range class plus iterator from scratch, according to how it works in Python's range().
Similar to Python you can use it three ways: Range(stop), Range(start, stop), Range(start, stop, step). All three support any negative value.
To test correctness of implementation I filled two unordered sets, one containing all generated values, another containing all used thread ids (to show that it actually used multi-core CPU execution).
Although I marked my iterator as random access type, still it is missing some methods like -= or -- operators, these extra methods are for further improvements. But for usage of std::for_each() it has enough methods.
If I made some mistakes of implementation please add comments to my answer with explanation.
Try it online!
#include <limits>
#include <execution>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <thread>
#include <unordered_set>
#include <string>
#include <sstream>
#include <mutex>
class Range {
public:
Range(ptrdiff_t start_stop, ptrdiff_t stop =
std::numeric_limits<ptrdiff_t>::max(), ptrdiff_t step = 1)
: step_(step) {
if (stop == std::numeric_limits<ptrdiff_t>::max()) {
start_ = 0;
stop_ = start_stop;
} else {
start_ = start_stop;
stop_ = stop;
}
if (step_ >= 0)
stop_ = std::max(start_, stop_);
else
stop_ = std::min(start_, stop_);
if (step_ >= 0)
stop_ = start_ + (stop_ - start_ + step_ - 1) / step_ * step_;
else
stop_ = start_ - (start_ - stop_ + step_ - 1) / (-step_) * (-step_);
}
class RangeIter {
public:
using iterator_category = std::random_access_iterator_tag;
using value_type = ptrdiff_t;
using difference_type = ptrdiff_t;
using pointer = ptrdiff_t const *;
using reference = ptrdiff_t const &;
RangeIter() {}
RangeIter(ptrdiff_t start, ptrdiff_t stop, ptrdiff_t step)
: cur_(start), stop_(stop), step_(step) {}
RangeIter & operator += (ptrdiff_t steps) {
cur_ += step_ * steps;
if (step_ >= 0)
cur_ = std::min(cur_, stop_);
else
cur_ = std::max(cur_, stop_);
return *this;
}
RangeIter operator + (ptrdiff_t steps) const {
auto it = *this;
it += steps;
return it;
}
ptrdiff_t operator [] (ptrdiff_t steps) const {
auto it = *this;
it += steps;
return *it;
}
ptrdiff_t operator - (RangeIter const & other) const {
return (cur_ - other.cur_) / step_;
}
RangeIter & operator ++ () {
*this += 1;
return *this;
}
ptrdiff_t const & operator * () const {
return cur_;
}
bool operator == (RangeIter const & other) const {
return cur_ == other.cur_;
}
bool operator != (RangeIter const & other) const {
return !(*this == other);
}
ptrdiff_t cur_ = 0, stop_ = 0, step_ = 0;
};
auto begin() const { return RangeIter(start_, stop_, step_); }
auto end() const { return RangeIter(stop_, stop_, step_); }
private:
ptrdiff_t start_ = 0, stop_ = 0, step_ = 0;
};
int main() {
ptrdiff_t start = 1, stop = 1000000, step = 2;
std::mutex mutex;
std::unordered_set<std::string> threads;
std::unordered_set<ptrdiff_t> values;
auto range = Range(start, stop, step);
std::for_each(std::execution::par, range.begin(), range.end(),
[&](int i) {
std::unique_lock<std::mutex> lock(mutex);
std::ostringstream ss;
ss << std::this_thread::get_id();
threads.insert(ss.str());
values.insert(i);
});
std::cout << "Threads:" << std::endl;
for (auto const & s: threads)
std::cout << s << std::endl;
{
bool correct = true;
size_t cnt = 0;
for (ptrdiff_t i = start; i < stop; i += step) {
++cnt;
if (!values.count(i)) {
correct = false;
std::cout << "No value: " << i << std::endl;
break;
}
}
if (values.size() != cnt)
std::cout << "Expected amount of values: " << cnt
<< ", actual " << values.size() << std::endl;
std::cout << "Correct values: " << std::boolalpha
<< (correct && (values.size() == cnt)) << std::endl;
}
}
Output:
Threads:
1628
9628
5408
2136
2168
8636
2880
6492
1100
Correct values: true
If the problem is in creating range similar to python's range() you can look through https://en.cppreference.com/w/cpp/iterator/iterator and use it's example:
#include <iostream>
#include <algorithm>
template<long FROM, long TO>
class Range {
public:
// member typedefs provided through inheriting from std::iterator
class iterator: public std::iterator<
std::input_iterator_tag, // iterator_category
long, // value_type
long, // difference_type
const long*, // pointer
long // reference
>{
long num = FROM;
public:
explicit iterator(long _num = 0) : num(_num) {}
iterator& operator++() {num = TO >= FROM ? num + 1: num - 1; return *this;}
iterator operator++(int) {iterator retval = *this; ++(*this); return retval;}
bool operator==(iterator other) const {return num == other.num;}
bool operator!=(iterator other) const {return !(*this == other);}
reference operator*() const {return num;}
};
iterator begin() {return iterator(FROM);}
iterator end() {return iterator(TO >= FROM? TO+1 : TO-1);}
};
int main() {
// std::find requires an input iterator
auto range = Range<15, 25>();
auto itr = std::find(range.begin(), range.end(), 18);
std::cout << *itr << '\n'; // 18
// Range::iterator also satisfies range-based for requirements
for(long l : Range<3, 5>()) {
std::cout << l << ' '; // 3 4 5
}
std::cout << '\n';
}
Just as an alternative, you could make each work package carry the necessary information by adding the index you need.
Example:
std::vector<std::pair<size_t, std::array<float, 2>>> values(1000);
for(size_t i = 0; i < values.size(); ++i) values[i].first = i;
std::for_each(std::execution::par, values.begin(), values.end(),
[resolution](auto& p) {
p.second[0] = static_cast<float>(p.first) / resolution;
p.second[1] = weierstrass(a, b, static_cast<float>(p.first) / resolution);
});
Not using indexing on values inside the threaded part like above may prevent false sharing and improve performance. You could also make each work package aligned to prevent false sharing to see if that has an effect on performance.
#include <new>
struct alignas(std::hardware_destructive_interference_size) workpackage {
size_t index;
std::array<float, 2> arr;
};
std::vector<workpackage> values(1000);
for(size_t i = 0; i < values.size(); ++i) values[i].index = i;
std::for_each(std::execution::par, values.begin(), values.end(),
[resolution](auto& wp) {
wp.arr[0] = static_cast<float>(wp.index) / resolution;
wp.arr[1] = weierstrass(a, b, static_cast<float>(wp.index) / resolution);
});
You can write your code in another way and drop any need for range at all like this:
std::vector<std::array<float, 2>> values(1000);
std::for_each(std::execution::par, values.begin(), values.end(), [&](std::array<float, 2>& val) {
auto i = std::distance(&values[0], &val);
val[0] = static_cast<float>(i) / resolution;
val[1] = weierstrass(a, b, static_cast<float>(i) / resolution);
});
I should say that this code is valid if and only if you are using std::for_each, because it is stated that:
Unlike the rest of the parallel algorithms, std::for_each is not allowed to make copies of the elements in the sequence even if they are trivially copyable.

Is there anything like an execute_if algorithm in stl?

Just as there are count_if and remove_if algorithms in STL, I'd like to find something like an execute_if, that would take the condition predicate separately from the function to execute.
For example I would like 1 line that prints all the odds using 2 lambdas, something like this:
auto vec = vector<int>{ 1,2,3,4,5 };
for_each_if(vec.begin(), vec.end(), [](auto val) {return val % 2 != 0; }, [](auto val) { cout << val; });
What's a good way to do this with algorithms?
If you have C++20 support, you can make use of the new range-library:
std::vector<int> vec{ 1,2,3,4,5 };
for (auto val : vec | std::views::filter([](auto val) { return val % 2 != 0; }))
{
std::cout << val;
}
Or (of course):
std::vector<int> vec{ 1,2,3,4,5 };
auto filtered = vec | std::views::filter([](auto val) {return val % 2 != 0; });
std::for_each(filtered.begin(), filtered.end(), [](auto val) { std::cout << val; } );
Live Demo
You could write such an algorithm yourself:
#include <iostream>
#include <algorithm>
#include <vector>
template <typename IT,typename predicate,typename func>
void for_each_if(IT begin,IT end,predicate p, func f){
std::for_each(begin,end,[&f,&p](auto val){
if (p(val)) f(val);
});
}
int main() {
auto vec = std::vector<int>{ 1,2,3,4,5 };
for_each_if(vec.begin(), vec.end(), [](auto val) {return val % 2 != 0; }, [](auto val) { std::cout << val; });
}
However, be prepared for a debatte on which one is actually more readable. I definitely prefer to pass only one functor to for_each instead of passing two functors to for_each_if.
separation of concerns. One caller would send ifodds, another caller would send ifevens. Both would send a printer.
You don't need an extra algorithm for that. The alternative to the above is:
auto condition = [](auto val) {return val % 2 != 0; };
auto func = [](auto val) { std::cout << val; };
auto if_fun = [&condition,&func](auto val){ if (condition(val)) func(val);};
std::for_each(vec.begin(),vec.end(),if_fun);
You can compose if_fun from different predicates and different functions as you wish.

How to chain C++ `transform` and `inner_product` calls?

I would like to do something like:
vector<int> v;
v.push_back(0);
v.push_back(1);
v
.transform([](auto i) { return i + 2; })
.transform([](auto i) { return i * 3; })
.inner_product(0);
In other words, just implicitly use begin() and end() for the first and last iterators and chain the results.
Is there anything (eg some library) that would allow this?
Just write your own class to augment std::vector.
#include <algorithm>
#include <iostream>
#include <numeric>
#include <vector>
#include <utility>
template < typename T >
class augmented_vector
{
std::vector < T > m_vec;
public:
augmented_vector() : m_vec() {}
explicit augmented_vector(std::vector<T> const& in) : m_vec(in) {}
void push_back ( T&& value )
{
m_vec.push_back(std::forward<T>(value));
}
template < typename F >
augmented_vector < T > transform(F const& f)
{
std::vector < T > new_vec(m_vec.size());
std::transform( m_vec.begin(), m_vec.end(), new_vec.begin(), f);
return augmented_vector < T > ( new_vec );
}
T inner_product(T value)
{
return std::inner_product( m_vec.begin(), m_vec.end(), m_vec.begin(), value);
}
};
int main()
{
augmented_vector<int> v;
v.push_back(0);
v.push_back(1);
auto val = v
.transform([](auto i) { return i + 2; })
.transform([](auto i) { return i * 3; })
.inner_product(0);
std::cout << val << '\n';
}
Or use the D programming language. It has universal function call syntax.
import std.algorithm : fold, map;
import std.stdio : writeln;
void main()
{
auto v = [0, 1];
auto x = v
.map!(a => a+2)
.map!(a => a*3)
.fold!((a,b) => a + b^^2)(0);
writeln(x);
}
Wrap the functions, providing the boilerplate in the wrapper functions:
template<typename Container, typename Transform>
void transform_container(Container & container, Transform transform) {
std::transform(std::begin(container), std::end(container),
std::begin(container), /* requires output iterator */
transform);
}
template<typename T, typename Container>
auto inner_product_self(Container&& container, T initial) {
return std::inner_product(std::begin(container), std::end(container),
std::begin(container),
initial);
}
Your code then becomes:
int main() {
std::vector<int> v(2);
std::itoa(std::begin(v), std::end(v), 0);
transform_container(v, [](auto i) { return i + 2; });
transform_container(v, [](auto i) { return i * 3; });
auto result = inner_product_self(container, 0);
std::cout << "result: " << result;
}
(Live on ideone)
You're not chained to object oriented programming!

Simpler way to set multiple array slots to one value

I'm coding in C++, and I have the following code:
int array[30];
array[9] = 1;
array[5] = 1;
array[14] = 1;
array[8] = 2;
array[15] = 2;
array[23] = 2;
array[12] = 2;
//...
Is there a way to initialize the array similar to the following?
int array[30];
array[9,5,14] = 1;
array[8,15,23,12] = 2;
//...
Note: In the actual code, there can be up to 30 slots that need to be set to one value.
This function will help make it less painful.
void initialize(int * arr, std::initializer_list<std::size_t> list, int value) {
for (auto i : list) {
arr[i] = value;
}
}
Call it like this.
initialize(array,{9,5,14},2);
A variant of aaronman's answer:
template <typename T>
void initialize(T array[], const T& value)
{
}
template <size_t index, size_t... indices, typename T>
void initialize(T array[], const T& value)
{
array[index] = value;
initialize<indices...>(array, value);
}
int main()
{
int array[10];
initialize<0,3,6>(array, 99);
std::cout << array[0] << " " << array[3] << " " << array[6] << std::endl;
}
Example: Click here
Just for the fun of it I created a somewhat different approach which needs a bit of infrastructure allowing initialization like so:
double array[40] = {};
"9 5 14"_idx(array) = 1;
"8 15 23 12"_idx(array) = 2;
If the digits need to be separated by commas, there is a small change needed. In any case, here is the complete code:
#include <algorithm>
#include <iostream>
#include <sstream>
#include <iterator>
template <int Size, typename T = int>
class assign
{
int d_indices[Size];
int* d_end;
T* d_array;
void operator=(assign const&) = delete;
public:
assign(char const* base, std::size_t n)
: d_end(std::copy(std::istream_iterator<int>(
std::istringstream(std::string(base, n)) >> std::skipws),
std::istream_iterator<int>(), this->d_indices))
, d_array()
{
}
assign(assign<Size>* as, T* a)
: d_end(std::copy(as->begin(), as->end(), this->d_indices))
, d_array(a) {
}
assign(assign const& o)
: d_end(std::copy(o.begin(), o.end(), this->d_indices))
, d_array(o.d_array)
{
}
int const* begin() const { return this->d_indices; }
int const* end() const { return this->d_end; }
template <typename A>
assign<Size, A> operator()(A* array) {
return assign<Size, A>(this, array);
}
void operator=(T const& value) {
for (auto it(this->begin()), end(this->end()); it != end; ++it) {
d_array[*it] = value;
}
}
};
assign<30> operator""_idx(char const* base, std::size_t n)
{
return assign<30>(base, n);
}
int main()
{
double array[40] = {};
"1 3 5"_idx(array) = 17;
"4 18 7"_idx(array) = 19;
std::copy(std::begin(array), std::end(array),
std::ostream_iterator<double>(std::cout, " "));
std::cout << "\n";
}
I just had a play around for the sake of fun / experimentation (Note my concerns at the bottom of the answer):
It's used like this:
smartAssign(array)[0][8] = 1;
smartAssign(array)[1][4][2] = 2;
smartAssign(array)[3] = 3;
smartAssign(array)[5][9][6][7] = 4;
Source code:
#include <assert.h> //Needed to test variables
#include <iostream>
#include <cstddef>
template <class ArrayPtr, class Value>
class SmartAssign
{
ArrayPtr m_array;
public:
class Proxy
{
ArrayPtr m_array;
size_t m_index;
Proxy* m_prev;
Proxy(ArrayPtr array, size_t index)
: m_array(array)
, m_index(index)
, m_prev(nullptr)
{ }
Proxy(Proxy* prev, size_t index)
: m_array(prev->m_array)
, m_index(index)
, m_prev(prev)
{ }
void assign(Value value)
{
m_array[m_index] = value;
for (auto prev = m_prev; prev; prev = prev->m_prev) {
m_array[prev->m_index] = value;
}
}
public:
void operator=(Value value)
{
assign(value);
}
Proxy operator[](size_t index)
{
return Proxy{this, index};
}
friend class SmartAssign;
};
SmartAssign(ArrayPtr array)
: m_array(array)
{
}
Proxy operator[](size_t index)
{
return Proxy{m_array, index};
}
};
template <class T>
SmartAssign<T*, T> smartAssign(T* array)
{
return SmartAssign<T*, T>(array);
}
int main()
{
int array[10];
smartAssign(array)[0][8] = 1;
smartAssign(array)[1][4][2] = 2;
smartAssign(array)[3] = 3;
smartAssign(array)[5][9][6][7] = 4;
for (auto i : array) {
std::cout << i << "\n";
}
//Now to test the variables
assert(array[0] == 1 && array[8] == 1);
assert(array[1] == 2 && array[4] == 2 && array[2] == 2);
assert(array[3] == 3);
assert(array[5] == 4 && array[9] == 4 && array[6] == 4 && array[7] == 4);
}
Let me know what you think, I don't typically write much code like this, I'm sure someone will point out some problems somewhere ;)
I'm not a 100% certain of the lifetime of the proxy objects.
The best you can do if your indexes are unrelated is "chaining" the assignments:
array[9] = array[5] = array[14] = 1;
However if you have some way to compute your indexes in a deterministic way you could use a loop:
for (size_t i = 0; i < 3; ++i)
array[transform_into_index(i)] = 1;
This last example also obviously applies if you have some container where your indexes are stored. So you could well do something like this:
const std::vector<size_t> indexes = { 9, 5, 14 };
for (auto i: indexes)
array[i] = 1;
Compilers which still doesn't support variadic template argument and universal initialization list, it can be a pain to realize, that some of the posted solution will not work
As it seems, OP only intends to work with arrays of numbers, valarray with variable arguments can actually solve this problem quite easily.
#include <valarray>
#include <cstdarg>
#include <iostream>
#include <algorithm>
#include <iterator>
template <std::size_t size >
std::valarray<std::size_t> selection( ... )
{
va_list arguments;
std::valarray<std::size_t> sel(size);
//Skip the first element
va_start ( arguments, size );
va_arg ( arguments, int );
for(auto &elem : sel)
elem = va_arg ( arguments, int );
va_end ( arguments );
return sel;
}
int main ()
{
//Create an array of 30 integers
std::valarray<int> array(30);
//The first argument is the count of indexes
//followed by the indexes of the array to initialize
array[selection<3>(9,5,14)] = 1;
array[selection<4>(8,15,13, 12)] = 2;
std::copy(std::begin(array), std::end(array),
std::ostream_iterator<int>(std::cout, " "));
return 0;
}
I remember, for static initialization exist syntax like:
int array[30] = {
[9] = 1, [8] = 2
}
And so on. This works in gcc, about another compilers - I do not know.
Use overload operator << .
#include <iostream>
#include <iomanip>
#include <cmath>
// value and indexes wrapper
template< typename T, std::size_t ... Ints> struct _s{ T value; };
//deduced value type
template< std::size_t ... Ints, typename T>
constexpr inline _s<T, Ints... > _ ( T const& v )noexcept { return {v}; }
// stored array reference
template< typename T, std::size_t N>
struct _ref
{
using array_ref = T (&)[N];
array_ref ref;
};
//join _s and _ref with << operator.
template<
template< typename , std::size_t ... > class IC,
typename U, std::size_t N, std::size_t ... indexes
>
constexpr _ref<U,N> operator << (_ref<U,N> r, IC<U, indexes...> ic ) noexcept
{
using list = bool[];
return ( (void)list{ false, ( (void)(r.ref[indexes] = ic.value), false) ... }) , r ;
//return r;
}
//helper function, for creating _ref<T,N> from array.
template< typename T, std::size_t N>
constexpr inline _ref<T,N> _i(T (&array)[N] ) noexcept { return {array}; }
int main()
{
int a[15] = {0};
_i(a) << _<0,3,4,5>(7) << _<8,9, 14>( 6 ) ;
for(auto x : a)std::cout << x << " " ;
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
//result: 7 0 0 7 7 7 0 0 6 6 0 0 0 0 6
double b[101]{0};
_i(b) << _<0,10,20,30,40,50,60,70,80,90>(3.14)
<< _<11,21,22,23,24,25>(2.71)
<< _<5,15,25,45,95>(1.414) ;
}
struct _i_t
{
int * array;
struct s
{
int* array;
std::initializer_list<int> l;
s const& operator = (int value) const noexcept
{
for(auto i : l )
array[i] = value;
return *this;
}
};
s operator []( std::initializer_list<int> i ) const noexcept
{
return s{array, i};
}
};
template< std::size_t N>
constexpr _i_t _i( int(&array)[N]) noexcept { return {array}; }
int main()
{
int a[15] = {0};
_i(a)[{1,3,5,7,9}] = 7;
for(auto x : a)std::cout << x << ' ';
}
Any fancy trickery you do will be unrolled by the compiler/assembler into exactly what you have. Are you doing this for readability reasons? If your array is already init, you can do:
array[8] = array[15] = array[23] = array[12] = 2;
But I stress my point above; it will be transformed into exactly what you have.