I am trying to understand expression templates to be able to efficiently evaluate multivariate polynomials like:
double PAx,PAy, aAB;
double val = (PAx*PAx*PAx*PAx + 6*PAx*PAX*aAB + 3*aAB*aAB)*(PAy*PAy*PAy*PAy);
using expression templates, if possible. How do I modify the following code snippet to achieve this goal?
#include <utility>
#include <iostream>
template<typename T1, typename T2>
struct plus;
template<typename T1, typename T2>
struct times;
template<typename T, typename U>
constexpr auto operator+(T l, U r){ return plus(l,r);}
template<typename T, typename U>
constexpr auto operator*(T l, U r){ return times(l,r);}
template<typename T1, typename T2>
struct plus{
constexpr plus(T1 t1_, T2 t2_):t1{t1_},t2{t2_}{}
T1 t1;
T2 t2;
template<typename T>
constexpr auto operator()(T&& t) const {return t1(std::forward<T>(t))+t2(std::forward<T>(t));}
};
template<typename T>
struct is_plus : std::false_type{};
template<typename T1, typename T2>
struct is_plus<plus<T1, T2>> : std::true_type{};
template<typename T1, typename T2>
struct times{
constexpr times(T1 t1_, T2 t2_):t1{t1_},t2{t2_}{}
T1 t1;
T2 t2;
template<typename T>
constexpr auto operator()(T&& t) const {return t1(std::forward<T>(t))*t2(std::forward<T>(t));}
};
template<typename T>
struct is_times : std::false_type{};
template<typename T1, typename T2>
struct is_times<times<T1, T2>> : std::true_type{};
template <typename T, typename U>
using same = std::is_same<typename std::decay<T>::type,typename std::decay<U>::type>;
int main(){
constexpr auto x = [](auto t){ return [t](auto t2){return t ;};};
constexpr auto y = [](auto t){ return [t](auto t2){return t2;};};
constexpr auto c = [](auto t){ return [t](auto){ return [t](auto){return t;};};};
constexpr auto zero = [](auto t){ return [t](auto t2){return 0 ;};};
constexpr auto one = [](auto t){ return [t](auto t2){return 1 ;};};
constexpr auto recursion = [one, zero, x, y](auto tag, auto t, auto self){
using t_t = decltype(t);
using x_t = decltype(x);
using y_t = decltype(y);
using tag_t = decltype(tag);
if constexpr (same<t_t,x_t>::value)
if constexpr (same<tag_t,x_t>::value)
return one;
else
return zero;
else if constexpr(same<t_t, y_t>::value)
if constexpr (same<tag_t,y_t>::value)
return one;
else
return zero;
else if constexpr( is_plus<typename std::decay<decltype(t)>::type>::value)
return plus{self(tag, t.t1, self), self(tag, t.t2, self)};
else if constexpr( is_times<typename std::decay<decltype(t)>::type>::value)
return plus{times{self(tag, t.t1, self), t.t2}, times{t.t1, self(tag, t.t2, self)}};
else
return zero;
};
constexpr auto Dx = [recursion,x](auto t){return recursion(x, t, recursion);};
constexpr auto Dy = [recursion,y](auto t){return recursion(y, t, recursion);};
// Having defined variables like:
double PAx,PAy,aAB;
auto constexpr ex = Dy(x*x*c(4.)+y*x*c(4.)); // I am interested in evaluating expressioons like (PAx*PAx*PAx*PAx + 6*PAx*PAx*aAB + 3*aAB*aAB)*(PAy*PAy*PAy*PAy)
auto constexpr val1=ex(5.);
auto constexpr val=(val1+x(1.))(0.);
std::cout<<val<<"\n";
}
The fact that in my application PAx, PAy and aAB, are only known at execution time can have a negative impact on the performance improvement that I am expecting? Can lazy evaluation be preserved?
The snippet is based on this tutorial I tested the snippet of code above on godbolt.org and here you can see the instantiation of the templates specializations.
Update
Taking into account the suggestions in the comments below, the code was modified as follows:
#include <utility>
#include <tuple>
#include <functional>
#include <iostream>
#include <cmath>
namespace expr_types {
template<class D>
struct formula {
constexpr auto operator()(auto...args) const {
return eval(*static_cast<D const*>(this))(args...);
}
};
template<std::size_t I>
struct var_t:formula<var_t<I>> {
friend constexpr auto eval( var_t ) {
return [](auto...args){return std::get<I>( std::make_tuple(args...) );};
}
};
template<std::size_t N>
constexpr var_t<N> var = {};
template<class T=double>
struct val_t:formula<val_t<T>> {
T k = {};
constexpr val_t(T in):k(in){}
friend constexpr auto eval( val_t self ) {
return [self](auto...){return self.k;};
}
friend constexpr auto diff( auto, val_t ) {
return val_t{0};
}
};
template<class T>
val_t(T)->val_t<T>;
template<class Lhs, class Op, class Rhs>
struct tree:formula<tree<Lhs, Op, Rhs>> {
Lhs lhs;
Op op;
Rhs rhs;
constexpr tree(Lhs l, Op o, Rhs r):lhs(l),op(o),rhs(r){}
friend constexpr auto eval( tree self ) {
return eval(self.lhs, self.op, self.rhs);
}
friend constexpr auto diff( auto D, tree self ) {
return diff(D, self.lhs, self.op, self.rhs);
}
};
auto operator+( auto lhs, auto rhs ) {
return tree{lhs, std::plus<>{}, rhs};
}
auto operator-( auto lhs, auto rhs ) {
return tree{lhs, std::minus<>{}, rhs};
}
auto operator*( auto lhs, auto rhs ) {
return tree{lhs, std::multiplies<>{}, rhs};
}
auto operator/( auto lhs, auto rhs ) {
return tree{lhs, std::divides<>{}, rhs};
}
struct pow_t {
auto operator()(auto lhs, auto rhs)const{
return std::pow(lhs, rhs);
}
};
auto operator^( auto lhs, auto rhs ) {
return tree{lhs, pow_t{}, rhs};
}
// what * means:
constexpr auto eval( auto lhs, auto op, auto rhs )
{
return [=](auto...args){
// use std::multiplies to avoid seeing above operator*
return op(eval(lhs)(args...), eval(rhs)(args...));
};
}
// What D(a*b) means:
constexpr auto diff(auto D, auto lhs, std::multiplies<>, auto rhs) {
return D(lhs)*rhs + lhs*D(rhs);
}
// What D(a/b) means:
constexpr auto diff(auto D, auto lhs, std::divides<>, auto rhs) {
return (D(lhs)*rhs - lhs*D(rhs)) / ( rhs^val_t{2} );
}
// What D(a+b) means:
constexpr auto diff(auto D, auto lhs, std::plus<>, auto rhs) {
return D(lhs)+D(rhs);
}
// What D(a-b) means:
constexpr auto diff(auto D, auto lhs, std::minus<>, auto rhs) {
return D(lhs)-D(rhs);
}
template<class V, class T=double>
struct differential_t {
constexpr auto operator()( auto f ) const {
return diff( *this, f );
}
};
template<std::size_t N, class T, std::size_t M>
constexpr val_t<T> diff( differential_t<var_t<N>, T>, var_t<M> ) {
return val_t<T>{N==M};
}
}
namespace expr {
constexpr auto x = expr_types::var<0>;
constexpr auto y = expr_types::var<1>;
constexpr auto val(auto x){
using namespace expr_types;
return val_t(x);
}
constexpr auto one = val(1);
constexpr auto zero = val(0);
template<auto V, class T=double>
constexpr expr_types::differential_t<decltype(V),T> D = {};
}
int main()
{
using namespace expr;
auto f = x*one + x*x + x*x*y + zero;
std::cout << f(1.0, 2.0) << "\n";
auto df = D<x>(f);
std::cout << df(1.0, 2.0) << "\n";
auto g = (one + x)*(one + y);
std::cout << g(1.0, 1.0) << "\n";
std::cout << D<y>(g)(1.0, 1.0) << "\n";
auto h = (x^val(2));
std::cout << h(2.0, 2.0) << "\n";
}
Related
I'm wondering if there is an elegant solution for composing mathematical operators in C++. By operator, I mean something like the following:
template<class H>
class ApplyOp {
H h;
public:
ApplyOp(){}
ApplyOp(H h_i) : h(h_i) {}
template<class argtype>
double operator()(argtype f,double x){
return h(x)*f(x);
}
};
The above class makes use of a "helper function" h(x). For example,
struct Helper{
Helper(){}
double operator()(double x){return x*x;}
};
struct F{
F(){}
double operator()(double x){return exp(x);}
};
int main()
{
Helper h;
F f;
ApplyOp<Helper> A(h);
std::cout<<"A(f,2.0) = "<<A(f,2.0)<<std::endl; //Returns 2^2*exp(2) = 29.5562...
return 0;
}
Now, I would like to compose the operator twice or more times, i.e. compute A^2(f,2.0). In the above example, this would return h(x)*h(x)*f(x). Note that this is not function composition, i.e. I do not want to compute A(A(f,2.0),2.0). Rather, think in terms of computing powers of a matrix: if h(x) = M (a matrix), I want M*M*...*M*x.
I was able to use std::bind() to achieve my desired result for A^2 (but not higher powers!) as follows:
auto g = std::bind(&ApplyOp<Helper>::operator()<F>,&A,f,std::placeholders::_1);
With the resulting g, I can apply A^2(f,2.0) by simply calling A(g,2.0). With the above examples, this would return h(x)*h(x)*f(x) = x*x*x*x*exp(x)
How would I generalize this to iteratively applying the operator A N times? I really liked the answer posted here, but it doesn't quite work here. I tried doing nested std:binds but quickly got into deep compiler errors.
Any ideas?
Complete working example:
#include<iostream>
#include<math.h>
#include<functional> //For std::bind
template<class H>
class ApplyOp {
H h;
public:
ApplyOp(){}
ApplyOp(H h_i) : h(h_i) {}
template<class argtype>
double operator()(argtype f,double x){
return h(x)*f(x);
}
};
struct Helper{
Helper(){}
double operator()(double x){return x*x;}
};
struct F{
F(){}
double operator()(double x){return exp(x);}
};
int main()
{
Helper h;
F f;
ApplyOp<Helper> A(h);
std::cout<<"A(f,2.0) = "<<A(f,2.0)<<std::endl; //Returns 2^2*exp(2) = 29.5562...
auto g = std::bind(&ApplyOp<Helper>::operator()<F>,&A,f,std::placeholders::_1);
std::cout<<"A^2(f,2.0) = "<<A(g,2.0) <<std::endl; //Returns 2^4*exp(2) = 118.225...
return 0;
}
Try this, using template specialization
#include<iostream>
#include<math.h>
#include<functional> //For std::bind
template<class H>
class ApplyOp {
H h;
public:
ApplyOp(){}
ApplyOp(H h_i) : h(h_i) {}
template<class argtype>
double operator()(argtype f,double x){
return h(x)*f(x);
}
};
struct Helper{
Helper(){}
double operator()(double x){return x*x;}
};
struct F{
F(){}
double operator()(double x){return exp(x);}
};
// C++ doesn't permit recursive "partial specialization" in function
// So, make it a struct instead
template<typename T, typename U, typename W, int i>
struct Binder {
auto binder(U b, W c) {
// Recursively call it with subtracting i by one
return [&](T x){ return b(Binder<T, U, W, i-1>().binder(b, c), x); };
}
};
// Specialize this "struct", when i = 2
template<typename T, typename U, typename W>
struct Binder<T, U, W, 2> {
auto binder(U b, W c) {
return [&](T x){ return b(c, x); };
}
};
// Helper function to call this struct (this is our goal, function template not
// struct)
template<int i, typename T, typename U, typename W>
auto binder(U b, W d) {
return Binder<T, U, W, i>().binder(b, d);
}
int main()
{
Helper h;
F f;
ApplyOp<Helper> A(h);
std::cout<<"A(f,2.0) = "<<A(f,2.0)<<std::endl; //Returns 2^2*exp(2) = 29.5562...
// We don't need to give all the template parameters, C++ will infer the rest
auto g = binder<2, double>(A, f);
std::cout<<"A^2(f,2.0) = "<<A(g,2.0) <<std::endl; //Returns 2^4*exp(2) = 118.225...
auto g1 = binder<3, double>(A, f);
std::cout<<"A^3(f,2.0) = "<<A(g1,2.0) <<std::endl; //Returns 2^6*exp(2) = 472.2
auto g2 = binder<4, double>(A, f);
std::cout<<"A^4(f,2.0) = "<<A(g2,2.0) <<std::endl; //Returns 2^8*exp(2) = 1891.598...
return 0;
}
From what I could understand from you question, you are essentially trying to define
A^1(h, f, x) = h(x) * f(x)
A^n(h, f, x) = h(x) * A^(n-1)(h, f, x)
If you are open to using C++17, here is something you can build upon:
#include <iostream>
#include <math.h>
template <int N>
struct apply_n_helper {
template <typename H, typename F>
auto operator()(H h, F f, double x) const {
if constexpr(N == 0) {
return f(x);
} else {
return h(x) * apply_n_helper<N - 1>()(h, f, x);
}
}
};
template <int N>
constexpr auto apply_n = apply_n_helper<N>();
int main() {
auto sqr = [](double x) { return x * x; };
auto exp_ = [](double x) { return exp(x); };
std::cout << apply_n<100>(sqr, exp_, 2.0) << '\n';
std::cout << apply_n<200>(sqr, exp_, 2.0) << '\n';
return 0;
}
If C++17 is not an option, you can easily rewrite this to use template specializations instead of constexpr-if. I will leave this as an exercise. Here is a link to compiler explorer with this code: https://godbolt.org/z/5ZMw-W
EDIT Looking back at this question, I see that you are essentially trying to compute (h(x))^n * f(x) in a manner so that you don't have to actually do any looping at runtime and the generated code is equivalent to something like:
auto y = h(x);
auto result = y * y * ... * y * f(x)
\_____________/
n times
return result;
Another way of achieving this would be to have something as follows
#include <cmath>
#include <iostream>
template <size_t N, typename T>
T pow(const T& x) {
if constexpr(N == 0) {
return 1;
} else if (N == 1) {
return x;
} else {
return pow<N/2>(x) * pow<N - N/2>(x);
}
}
template <int N>
struct apply_n_helper {
template <typename H, typename F>
auto operator()(H h, F f, double x) const {
auto tmp = pow<N>(h(x));
return tmp * f(x);
}
};
template <int N>
constexpr auto apply_n = apply_n_helper<N>();
int main()
{
auto sqr = [](double x) { return x * x; };
auto exp_ = [](double x) { return exp(x); };
std::cout << apply_n<100>(sqr, exp_, 2.0) << '\n';
std::cout << apply_n<200>(sqr, exp_, 2.0) << '\n';
return 0;
}
Here, the usage of pow function is saving us from evaluating h(x) several times.
I mean you can use a other class:
template <typename T, std::size_t N>
struct Pow
{
Pow(T t) : t(t) {}
double operator()(double x) const
{
double res = 1.;
for (int i = 0; i != N; ++i) {
res *= t(x);
}
return res;
}
T t;
};
And use
ApplyOp<Pow<Helper, 2>> B(h); instead of ApplyOp<Helper> A(h);
Demo
So you want to be able to multiply functions. Well, that sounds good. Why not + and - and / while we are in there?
template<class F>
struct alg_fun;
template<class F>
alg_fun<F> make_alg_fun( F f );
template<class F>
struct alg_fun:F {
alg_fun(F f):F(std::move(f)){}
alg_fun(alg_fun const&)=default;
alg_fun(alg_fun &&)=default;
alg_fun& operator=(alg_fun const&)=default;
alg_fun& operator=(alg_fun &&)=default;
template<class G, class Op>
friend auto bin_op( alg_fun<F> f, alg_fun<G> g, Op op ) {
return make_alg_fun(
[f=std::move(f), g=std::move(g), op=std::move(op)](auto&&...args){
return op( f(decltype(args)(args)...), g(decltype(args)(args)...) );
}
);
}
template<class G>
friend auto operator+( alg_fun<F> f, alg_fun<G> g ) {
return bin_op( std::move(f), std::move(g), std::plus<>{} );
}
template<class G>
friend auto operator-( alg_fun<F> f, alg_fun<G> g ) {
return bin_op( std::move(f), std::move(g), std::minus<>{} );
}
template<class G>
friend auto operator*( alg_fun<F> f, alg_fun<G> g ) {
return bin_op( std::move(f), std::move(g),
std::multiplies<>{} );
}
template<class G>
friend auto operator/( alg_fun<F> f, alg_fun<G> g ) {
return bin_op( std::move(f), std::move(g),
std::divides<>{} );
}
template<class Rhs,
std::enable_if_t< std::is_convertible<alg_fun<Rhs>, F>{}, bool> = true
>
alg_fun( alg_fun<Rhs> rhs ):
F(std::move(rhs))
{}
// often doesn't compile:
template<class G>
alg_fun& operator-=( alg_fun<G> rhs )& {
*this = std::move(*this)-std::move(rhs);
return *this;
}
template<class G>
alg_fun& operator+=( alg_fun<G> rhs )& {
*this = std::move(*this)+std::move(rhs);
return *this;
}
template<class G>
alg_fun& operator*=( alg_fun<G> rhs )& {
*this = std::move(*this)*std::move(rhs);
return *this;
}
template<class G>
alg_fun& operator/=( alg_fun<G> rhs )& {
*this = std::move(*this)/std::move(rhs);
return *this;
}
};
template<class F>
alg_fun<F> make_alg_fun( F f ) { return {std::move(f)}; }
auto identity = make_alg_fun([](auto&& x){ return decltype(x)(x); });
template<class X>
auto always_return( X&& x ) {
return make_alg_fun([x=std::forward<X>(x)](auto&&... /* ignored */) {
return x;
});
}
I think that about does it.
auto square = identity*identity;
we can also have type-erased alg funs.
template<class Out, class...In>
using alg_map = alg_fun< std::function<Out(In...)> >;
these are the ones that support things like *=. alg_fun in general don't type erase enough to.
template<class Out, class... In>
alg_map<Out, In...> pow( alg_map<Out, In...> f, std::size_t n ) {
if (n==0) return always_return(Out(1));
auto r = f;
for (std::size_t i = 1; i < n; ++i) {
r *= f;
}
return r;
}
could be done more efficiently.
Test code:
auto add_3 = make_alg_fun( [](auto&& x){ return x+3; } );
std::cout << (square * add_3)(3) << "\n";; // Prints 54, aka 3*3 * (3+3)
alg_map<int, int> f = identity;
std::cout << pow(f, 10)(2) << "\n"; // prints 1024
Live example.
Here is a more efficient pow that works without type erasure:
inline auto raise(std::size_t n) {
return make_alg_fun([n](auto&&x)
-> std::decay_t<decltype(x)>
{
std::decay_t<decltype(x)> r = 1;
auto tmp = decltype(x)(x);
std::size_t bit = 0;
auto mask = n;
while(mask) {
if ( mask & (1<<bit))
r *= tmp;
mask = mask & ~(1<<bit);
tmp *= tmp;
++bit;
}
return r;
});
}
template<class F>
auto pow( alg_fun<F> f, std::size_t n ) {
return compose( raise(n), std::move(f) );
}
Live example. It uses a new function compose in alg_fun:
template<class G>
friend auto compose( alg_fun lhs, alg_fun<G> rhs ) {
return make_alg_fun( [lhs=std::move(lhs), rhs=std::move(rhs)](auto&&...args){
return lhs(rhs(decltype(args)(args)...));
});
}
which does compose(f,g)(x) := f(g(x))
Your code now literally becomes
alg_fun<Helper> h;
alg_fun<F> f;
auto result = pow( h, 10 )*f;
which is h(x)*h(x)*h(x)*h(x)*h(x)*h(x)*h(x)*h(x)*h(x)*h(x)*f(x). Except (with the efficient version) I only call h once and just raise the result to the power 10.
I want to know if it is possible to chain a bunch of lambdas so that I can use them all at once, something like this:
std::vector<int> vec{1, 2, 3};
auto f1 = [](int i){return i == 1;};
auto f2 = [](int i){return i == 2;};
// What template type can I use for logical_or ?
auto f = combine(std::logical_or, f1, f2);
vec.erase(std::remove_if(vec.begin(), vec.end(), f), vec.end());
So far I was thinking something like this:
template <typename OP, typename T>
T combine(const OP& op, const T& t1, const T& t2) {
return t1 op t2;
}
template <typename OP, typename T, typename... Ts>
T combine(const OP& op, const T& t1, const T& t2, const Ts&... ts) {
return t1 op t2 op combine(op, ts...);
}
But lambda || lambda doesn't make sense.
Is what I'm trying to do possible?
You might create class to do the job:
template <typename ... Fs>
struct AnyOf
{
public:
AnyOf(Fs... fs) : fs(fs...) {};
template <typename ...Ts>
decltype(auto) operator()(Ts&&... args) const
{
return call(std::index_sequence_for<Fs...>(), std::forward<Ts>(args)...);
}
private:
template <std::size_t ...Is, typename ...Ts>
decltype(auto) call(std::index_sequence<Is...>, Ts&&... args) const
{
return (... || std::get<Is>(fs)(args...));
}
private:
std::tuple<Fs...> fs;
};
With usage:
auto f = AnyOf(f1, f2); // C++17 way.
//previously, template function `MakeAnyOf` should be implemented
Demo
template<class F>
struct logical_f;
template<class F>
logical_f<F> logical( F f ){
return std::move(f);
}
template<class F>
struct logical_f:F{
logical_f(F in):F(std::move(in)){}
template<class O>
friend auto operator||( logical_f self, logical_f<O> const& o ) {
auto r = [lhs=std::move(self.f), rhs=o.f](auto&&...args)->bool{
return lhs(args...)||rhs(args...);
};
return logical(std::move(r));
};
now you can do:
auto f = logical(f1) || logical(f2);
In c++17 you can fold over ||; prior to that you can use hand written functions that do the folding.
try this functional one-liner (it use fold expression so it's c++17)
auto any_combiner = [](auto&& ...fs){return [=](auto&& ...params){return (fs(params...) ||...);};};
auto any = any_combiner(f1,f2);
Wandbox example
or you can simply use std::any_of for || (and std::all_of for &&), and it's c++11
vector<function<bool(int)>> fs = {f1,f2};
auto any = [&](int i){return std::any_of(fs.begin(),fs.end(),[i](function<bool(int)>&f){return f(i);});};
or what's wrong with plain old for, it so simple! (basically std::any_of)
vector<function<bool(int)>> fs = {f1,f2};
auto any = [&](int i){
for(auto& f:fs)
if(f(i))
return true;
return false;
};
I have a class that defines a constexpr size_t dimensions. In this class, I implemented an EvaluateOver<Dimensions...>(Lambda F) which does something over the dimensions I specify. For example, say dimensions=4 and f is some lambda expression:
MyClass.EvaluateOver<0,2>(f);
will perform f with respect to 0 and 2 by doing the following expansion:
template<size_t... Dims, typename Lambda>
inline auto EvaluateOver(const Lambda& F) const
{
F(std::get<Dims>(_memberTupleDataContainer)...);
}
Now I want another member function that will evaluate over the unspecified dimensions. So EvaluateOverOthers<0,2>(f) will perform its operation on dimensions 1 and 3.
Ideally, I am thinking of the following:
template<size_t... Dims, typename Lambda>
inline auto EvaluateOverOthers(const Lambda& F) const
{
EvaluateOver<
// variadic parameter that does the mathematical complement of
// Dims... with a variadic expansion of dimensions
>(F);
}
Following might help:
namespace details
{
template <typename Seq1, typename Seq2, typename Res = std::index_sequence<>>
struct minus;
// Nothing more to remove
template <std::size_t ... Is1, std::size_t... IRes>
struct minus<std::index_sequence<Is1...>,
std::index_sequence<>,
std::index_sequence<IRes...>>
{
using type = std::index_sequence<IRes..., Is1...>;
};
// Remove front elements as they are equal.
template <std::size_t I, std::size_t ... Is1, std::size_t ... Is2, std::size_t... IRes>
struct minus<std::index_sequence<I, Is1...>,
std::index_sequence<I, Is2...>,
std::index_sequence<IRes...>>
{
using type = typename minus<std::index_sequence<Is1...>,
std::index_sequence<Is2...>,
std::index_sequence<IRes...>>::type;
};
// Add front element to result.
template <std::size_t I1, std::size_t I2,
std::size_t ... Is1, std::size_t ... Is2,
std::size_t... IRes>
struct minus<std::index_sequence<I1, Is1...>,
std::index_sequence<I2, Is2...>,
std::index_sequence<IRes...>>
{
using type = typename minus<std::index_sequence<Is1...>,
std::index_sequence<I2, Is2...>,
std::index_sequence<IRes..., I1>>::type;
};
}
template <std::size_t N, typename Seq>
using complement = details::minus<std::make_index_sequence<N>, Seq>;
template <std::size_t N, typename Seq>
using complement_t = typename complement<N, Seq>::type;
// Some test
static_assert(std::is_same<std::index_sequence<0, 3>,
complement_t<4, std::index_sequence<1, 2>>>::value, "!");
And then
template<size_t... Is, typename Lambda>
auto EvaluateOver(const Lambda& F, std::index_sequence<Is...>) const
{
return F(std::get<Is>(_memberTupleDataContainer)...);
}
template<size_t... Dims, typename Lambda>
auto EvaluateOver(const Lambda& F) const
{
return EvaluateOver(F, std::index_sequence<Is...>{});
}
template<size_t... Is, typename Lambda>
auto EvaluateOverOthers(const Lambda& F) const
{
return EvaluateOver(F, complement_t<_dimension, std::index_sequence<Is...>>{});
}
I played with a constexpr C++17 solution (online demo). I think the logic for complements could be factored out (if desired).
#include <iostream>
#include <iterator>
#include <utility>
#include "my_constexpr_array.hpp"
template<size_t Nd>
struct MyClass {
static constexpr auto dim_arr = Array(std::make_index_sequence<Nd>{});
template<size_t... excludes, class F>
auto eval_others(F f) const {
constexpr auto excl_arr = Array{excludes...};
constexpr auto incl_pred = [&] (size_t i) { return !excl_arr.contains(i); };
constexpr auto incl_excl_arr = dim_arr.partition(incl_pred);
constexpr auto incl_count = dim_arr.count_if(incl_pred);
return eval_helper(
f,
[&] { return incl_excl_arr; },// wrapped in lambda to preserve constexpr
std::make_index_sequence<incl_count>{}// indices for trimming incl_excl_arr
);
}
template<class F, class Dims, size_t... is>
auto eval_helper(F f, Dims dims, std::index_sequence<is...>) const {
return f(std::integral_constant<size_t, dims()[is]>{}...);
}
};
int main() {
MyClass<7> foo{};
foo.eval_others<2, 4>([&] (auto... is) { (std::cout << ... << is) << "\n"; });
return 0;
}
Where "my_constexpr_array.hpp" would behave like
template<class T, size_t size>
struct Array {
static_assert(size >= 1);
T data_[size];
constexpr Array() noexcept
: data_{} {}
template<class... Ts>
explicit constexpr Array(T v0, Ts... vs) noexcept
: data_{v0, vs...} {}
template<T... vs>
explicit constexpr Array(std::integer_sequence<T, vs...>) noexcept
: data_{vs...} {}
constexpr T* begin() { return data_; }
constexpr const T* begin() const { return data_; }
constexpr T* end() { return begin() + size; }
constexpr const T* end() const { return begin() + size; }
constexpr decltype(auto) operator[](size_t i) { return data_[i]; }
constexpr decltype(auto) operator[](size_t i) const { return data_[i]; }
constexpr bool contains(const T& v) const {
for(auto& x : *this) if(x == v) return true;
return false;
}
template<class Pred>
constexpr size_t count_if(Pred pred) const {
size_t result = 0;
for(auto& x : *this) result += size_t(pred(x) ? 1 : 0);
return result;
}
template<class Pred>
constexpr Array partition(Pred pred) const {
// return a sorted copy such that all `true`s precede all `false`s
Array result{};
T* true_false_dst[2] = {result.begin(), result.begin() + count_if(pred)};
// pair of output iterators; use first if predicate is true and vice versa
for(auto& x : *this) *true_false_dst[pred(x) ? 0 : 1]++ = x;
return result;
}
friend std::ostream& operator<<(std::ostream& os, const Array& self) {
for(auto& x : self) os << x << ", ";
return os;
}
};
template<class T, class... Ts>
Array(T, Ts...) -> Array<T, size_t(1) + sizeof...(Ts)>;
template<class T, T... vs>
Array(std::integer_sequence<T, vs...>) -> Array<T, sizeof...(vs)>;
I've written a for_each for tuples:
template <typename Tuple, typename F, size_t begin, size_t end>
enable_if_t<begin == end || tuple_size<Tuple>::value < end> for_each(Tuple&, F&&) {
}
template <typename Tuple, typename F, size_t begin = 0U, size_t end = tuple_size<Tuple>::value>
enable_if_t<begin < end && tuple_size<Tuple>::value >= end> for_each(Tuple& t, F&& f) {
f(get<begin>(t));
for_each<Tuple, F, begin + 1, end>(t, forward<F>(f));
}
[Live Example]
But Yakk's answer to this question gives a wonderful example of how to handle running a lambda on all tuple values non-recursively:
namespace detail {
template<class F, class...Args>
void for_each_arg(F&& f, Args&&...args) {
using detail = int[];
static_cast<void>(detail{((f(std::forward<Args>(args))), void(), 0)..., 0});
}
}
template <typename F, typename Tuple>
void for_each_tuple_element(F&& f, Tuple&& t) {
return experimental::apply([&](auto&&...args) { detail::for_each_arg(forward<F>(f), decltype(args)(args)... ); }, forward<Tuple>(t));
}
This requires apply. You can see my simplification of Yakk's answer here: http://ideone.com/yAYjmw
My question is this: Is there a way to somehow retrofit for_each_tuple_element with a range, avoiding the recursion that my code incurs? I've tried constructing the subset of the tuple defined by the range, but I can't seem to do that without using recursion, and then why not just my for_each?
You could avoid recursion by generating a sequence of calls to the std::get<Is>(t)... function, with Is indices ranging from begin up to end-1. It's fairly easy to generate a sequence of consecutive numbers starting at a given index, as it's enough to make the starting point an offset that is then added to each item of a regular index sequence, e.g.:
std::get<begin + 0>(t), std::get<begin + 1>(t), ... std::get<begin + n>(t)
where the length of the sequence is equal to the distance between begin and end.
#include <tuple>
#include <type_traits>
#include <utility>
#include <cstddef>
#include <limits>
template <std::size_t begin, typename Tuple, typename F, std::size_t... Is>
void for_each(Tuple&& t, F&& f, std::index_sequence<Is...>)
{
using expand = int[];
static_cast<void>(expand{ 0, (f(std::get<begin + Is>(std::forward<Tuple>(t))), void(), 0)... });
}
template <std::size_t begin = 0U, std::size_t end = std::numeric_limits<std::size_t>::max(), typename Tuple, typename F>
void for_each(Tuple&& t, F&& f)
{
for_each<begin>(std::forward<Tuple>(t), std::forward<F>(f)
, std::make_index_sequence<(end==std::numeric_limits<std::size_t>::max()?std::tuple_size<std::decay_t<Tuple>>::value:end)-begin>{});
}
Test:
int main()
{
auto t = std::make_tuple(3.14, "Hello World!", -1);
auto f = [](const auto& i) { std::cout << i << ' '; };
for_each<1>(t, f);
for_each<1,3>(t, f);
for_each<0,2>(t, f);
}
DEMO
Also, note that defaulted template parameters of a function template don't have to be placed at the end of a template parameter list, hence, you can avoid the ugly decltype(t), decltype(f) part. This implies that end cannot be defaulted to std::tuple_size<Tuple>::value (since Tuple goes after end), but at this point all you need is some default magic number.
You could implement a make_index_range metafunction like so:
template <std::size_t Start, std::size_t End>
struct index_range {
template <std::size_t... Idx>
static std::index_sequence<(Idx + Start)...>
make_range (std::index_sequence<Idx...>);
using type = decltype(make_range(std::make_index_sequence<End-Start>{}));
};
template <std::size_t Start, std::size_t End>
using make_index_range = typename index_range<Start, End>::type;
Then you can use this to generate your std::index_sequence:
template <typename Tuple, typename F, std::size_t... Idx>
void for_each(Tuple& t, F&& f, std::index_sequence<Idx...>) {
(void)std::initializer_list<int> {
(std::forward<F>(f)(std::get<Idx>(t)), 0)...
};
}
template <typename Tuple, size_t begin = 0U,
size_t end = tuple_size<Tuple>::value, typename F>
enable_if_t<begin < end && tuple_size<Tuple>::value >= end>
for_each(Tuple& t, F&& f) {
for_each(t, std::forward<F>(f), make_index_range<begin, end>{});
}
You would use this like so:
auto t = std::make_tuple(1, 42.1, "hello world");
for_each<decltype(t), 2, 3>(t,[](auto e){std::cout << e << '\n';});
//outputs hello world
Note that you need to pass decltype(t) in if you want to give a begin and end. You could avoid that by using the technique in Peter Skotnicki's answer.
Live Demo
Inspired by #Piotr, but with extra prettiness :-)
#include <tuple>
#include <utility>
#include <tuple>
#include <cstddef>
#include <string>
#include <iostream>
template<class Tuple, size_t I>
struct tuple_iterator
{
constexpr tuple_iterator(Tuple& p) : _p(p) {}
static constexpr auto index() { return I; }
constexpr auto operator++() const { return tuple_iterator<Tuple, I+1>(_p); }
constexpr auto operator--() const { return tuple_iterator<Tuple, I-1>(_p); }
constexpr decltype(auto) deref() const { return _p; }
Tuple& _p;
};
template<class...Ts>
constexpr auto begin(const std::tuple<Ts...>& t) {
return tuple_iterator<const std::tuple<Ts...>, 0>(t);
}
template<class...Ts>
constexpr auto end(const std::tuple<Ts...>& t) {
return tuple_iterator<const std::tuple<Ts...>, sizeof...(Ts)>(t);
}
template<class Tuple, size_t I>
constexpr auto prev(tuple_iterator<Tuple, I> it) { return --it; }
template<class Tuple, size_t I>
constexpr auto next(tuple_iterator<Tuple, I> it) { return ++it; }
namespace detail
{
template <
std::size_t begin,
typename Tuple,
typename F,
std::size_t... Is
>
void for_each(Tuple&& t, F&& f, std::index_sequence<Is...>)
{
using expand = int[];
static_cast<void>(expand{ 0, (f(std::get<begin + Is>(std::forward<Tuple>(t))), void(), 0)... });
}
}
template<class Tuple, size_t First, size_t Last, class Func>
void for_each(tuple_iterator<Tuple, First> first, tuple_iterator<Tuple, Last> last, Func&& f)
{
constexpr auto dist = Last - First;
constexpr auto base = First;
constexpr auto extent = std::make_index_sequence<dist>();
detail::for_each<base>(first.deref(), std::forward<Func>(f), extent);
}
int main()
{
using namespace std;
auto x = make_tuple("dont print me", 1, "two", "three"s, "or me");
for_each(next(begin(x)), prev(end(x)), [](const auto& x) { cout << x << endl; });
return 0;
}
expected results:
1
two
three
Sorry about the second answer, but I felt that this was worth it.
Presenting the function make_poly_tuple_iterator() which returns an iterator that will iterate over elements in a tuple, working with all algorithms in the std namespace.
de-referencing the iterator results in a boost::variant< reference types... > so the functors will have to be specialisations of boost::static_visitor<>.
Demo below, use like this:
int main()
{
using namespace std;
auto x = make_tuple(tagged_string<tag1>("dont print me"),
1,
2.0,
tagged_string<tag2>("three"),
tagged_string<tag3>("or me"));
// this is the statically typed version
for_each(next(begin(x)),
prev(end(x)),
[](const auto& x) { cout << x << endl; });
// and the polymorphic version
auto first = std::next(make_poly_tuple_iterator(begin(x)));
auto last = std::prev(make_poly_tuple_iterator(end(x)));
// note: std::for_each ;-)
std::for_each(first, last, print_it());
return 0;
}
expected results:
1
2
three
printing: 1
printing: 2
printing: three
Complete code:
Yes I know, there are many, many improvements that can be made....
#include <tuple>
#include <utility>
#include <tuple>
#include <cstddef>
#include <string>
#include <iostream>
#include <boost/variant.hpp>
#include <stdexcept>
#include <exception>
template<class Tuple, size_t I>
struct tuple_iterator
{
constexpr tuple_iterator(Tuple& p) : _p(p) {}
static constexpr auto index() { return I; }
static constexpr auto upper_bound() { return std::tuple_size<Tuple>::value; }
static constexpr auto lower_bound() { return 0; }
template<size_t I2> static constexpr auto ValidIndex = I2 >= lower_bound() && I2 < upper_bound();
template<size_t I2, typename = void>
struct de_ref_type { using type = decltype(std::get<0>(std::declval<Tuple>())); };
template<size_t I2>
struct de_ref_type<I2, std::enable_if_t<ValidIndex<I2>>>
{ using type = decltype(std::get<I2>(std::declval<Tuple>())); };
template<size_t I2> using DerefType = typename de_ref_type<I2>::type;
constexpr auto operator++() const { return make_like_me<I+1>(); }
constexpr auto operator--() const { return make_like_me<I-1>(); }
template<size_t I2, std::enable_if_t<(I2 < lower_bound())>* = nullptr>
constexpr auto make_like_me() const { return tuple_iterator<Tuple, lower_bound()>(_p); }
template<size_t I2, std::enable_if_t<(I2 >= upper_bound())>* = nullptr>
constexpr auto make_like_me() const { return tuple_iterator<Tuple, upper_bound()>(_p); }
template<size_t I2, std::enable_if_t<ValidIndex<I2>>* = nullptr>
constexpr auto make_like_me() const { return tuple_iterator<Tuple, I2>(_p); }
constexpr decltype(auto) deref() const { return _p; }
template<size_t X> bool operator==(const tuple_iterator<Tuple, X>& r) const { return false; }
bool operator==(const tuple_iterator<Tuple, I>& r) const {
return std::addressof(_p) == std::addressof(r._p);
}
template<size_t I2, std::enable_if_t<ValidIndex<I2>>* =nullptr>
DerefType<I2> impl_star() const { return std::get<I2>(_p); }
template<size_t I2, std::enable_if_t<not ValidIndex<I2>>* =nullptr>
DerefType<I2> impl_star() const
{ throw std::logic_error("out of range"); }
decltype(auto) operator*() const {
return impl_star<index()>();
}
Tuple& _p;
};
template<class...Ts>
constexpr auto begin(const std::tuple<Ts...>& t) {
return tuple_iterator<const std::tuple<Ts...>, 0>(t);
}
template<class...Ts>
constexpr auto end(const std::tuple<Ts...>& t) {
return tuple_iterator<const std::tuple<Ts...>, sizeof...(Ts)>(t);
}
template<class Tuple, size_t I>
constexpr auto prev(tuple_iterator<Tuple, I> it) { return --it; }
template<class Tuple, size_t I>
constexpr auto next(tuple_iterator<Tuple, I> it) { return ++it; }
namespace detail
{
template <
std::size_t begin,
typename Tuple,
typename F,
std::size_t... Is
>
void for_each(Tuple&& t, F&& f, std::index_sequence<Is...>)
{
using expand = int[];
static_cast<void>(expand{ 0, (f(std::get<begin + Is>(std::forward<Tuple>(t))), void(), 0)... });
}
}
template<class Tuple, size_t First, size_t Last, class Func>
void for_each(tuple_iterator<Tuple, First> first, tuple_iterator<Tuple, Last> last, Func&& f)
{
constexpr auto dist = Last - First;
constexpr auto base = First;
constexpr auto extent = std::make_index_sequence<dist>();
detail::for_each<base>(first.deref(), std::forward<Func>(f), extent);
}
namespace detail {
template<class Tuple>
struct variant_of_tuple;
template<class...Ts>
struct variant_of_tuple<std::tuple<Ts...>>
{
// todo: some work to remove duplicates
using type = boost::variant<std::add_lvalue_reference_t<Ts>...>;
};
template<class...Ts>
struct variant_of_tuple<const std::tuple<Ts...>>
{
// todo: some work to remove duplicates
using type = boost::variant<std::add_lvalue_reference_t<std::add_const_t<Ts>>...>;
};
}
template<class Tuple>
using ToVariant = typename detail::variant_of_tuple<Tuple>::type;
template<class Tuple>
struct poly_tuple_iterator
{
using tuple_type = Tuple;
using value_type = ToVariant<std::remove_reference_t<tuple_type>>;
using difference_type = std::ptrdiff_t;
using pointer = value_type*;
using reference = value_type&;
using iterator_category = std::random_access_iterator_tag;
struct concept {
virtual ~concept() = default;
virtual const std::type_info& type() const = 0;
virtual const void* address() const = 0;
virtual bool equal(const void* p) const = 0;
virtual std::unique_ptr<concept> clone() const = 0;
virtual std::unique_ptr<concept> next() const = 0;
virtual std::unique_ptr<concept> prev() const = 0;
virtual value_type deref() const = 0;
};
template<size_t I>
struct model : concept {
using my_type = tuple_iterator<tuple_type, I>;
model(my_type iter) : _iter(iter) {}
const std::type_info& type() const override { return typeid(_iter); }
const void* address() const override { return std::addressof(_iter); }
std::unique_ptr<concept> clone() const override { return std::make_unique<model<I>>(_iter); };
std::unique_ptr<concept> next() const override {
auto next_iter = ++_iter;
return std::make_unique<model<next_iter.index()>>(next_iter);
};
std::unique_ptr<concept> prev() const override {
auto next_iter = --_iter;
return std::make_unique<model<next_iter.index()>>(next_iter);
};
value_type deref() const override { return { *_iter }; }
bool equal(const void* p) const override {
return _iter == *reinterpret_cast<const my_type*>(p);
}
my_type _iter;
};
template<size_t I>
poly_tuple_iterator(tuple_iterator<tuple_type, I> iter)
: _impl(std::make_unique<model<I>>(iter))
{}
poly_tuple_iterator(const poly_tuple_iterator& r) : _impl(r._impl->clone()) {};
poly_tuple_iterator(poly_tuple_iterator&& r) : _impl(std::move(r._impl)) {};
poly_tuple_iterator& operator=(const poly_tuple_iterator& r) {
_impl = r._impl->clone();
return *this;
}
poly_tuple_iterator& operator=(poly_tuple_iterator&& r) {
auto tmp = r._impl->clone();
std::swap(tmp, _impl);
return *this;
}
value_type operator*() const { return _impl->deref(); }
poly_tuple_iterator& operator++() { _impl = _impl->next(); return *this; }
poly_tuple_iterator operator++(int) { auto tmp = *this; _impl = _impl->next(); return tmp; }
poly_tuple_iterator& operator--() { _impl = _impl->prev(); return *this; }
poly_tuple_iterator operator--(int) { auto tmp = *this; _impl = _impl->prev(); return tmp; }
poly_tuple_iterator& operator+=(difference_type dist) {
while (dist > 0) {
++(*this);
--dist;
}
while(dist < 0) {
--(*this);
++dist;
}
return *this;
}
bool operator==(const poly_tuple_iterator& r) const {
return _impl->type() == r._impl->type()
and _impl->equal(r._impl->address());
}
bool operator!=(const poly_tuple_iterator& r) const {
return not (*this == r);
}
private:
std::unique_ptr<concept> _impl;
};
template<class Tuple, size_t I>
auto make_poly_tuple_iterator(tuple_iterator<Tuple, I> iter) {
return poly_tuple_iterator<Tuple>(iter);
}
struct print_it : boost::static_visitor<void>
{
template<class T>
void operator()(const T& t) const {
std::cout << "printing: " << t << std::endl;
}
template<class...Ts>
void operator()(const boost::variant<Ts...>& v) const {
boost::apply_visitor(*this, v);
}
};
// to differentiate string types for this demo
template<class tag>
struct tagged_string : std::string
{
using std::string::string;
};
struct tag1 {};
struct tag2 {};
struct tag3 {};
int main()
{
using namespace std;
auto x = make_tuple(tagged_string<tag1>("dont print me"),
1,
2.0,
tagged_string<tag2>("three"),
tagged_string<tag3>("or me"));
for_each(next(begin(x)), prev(end(x)), [](const auto& x) { cout << x << endl; });
auto first = std::next(make_poly_tuple_iterator(begin(x)));
auto last = std::prev(make_poly_tuple_iterator(end(x)));
std::for_each(first, last, print_it());
return 0;
}
I want to do the following:
std::vector<int> a = {1,2,3}, b = {4,5,6}, c = {7,8,9};
for(auto&& i : join(a,b,c)) {
i += 1
std::cout << i; // -> 2345678910
}
I tried using boost::range::join, this works fine:
auto r = boost::join(a,b);
for(auto&& i : boost::join(r,c)) {
i += 1;
std::cout << i; // -> 2345678910
}
Chaining joins, reading operations work:
for(auto&& i : boost::join(boost::join(a,b),c))
std::cout << i; // -> 123456789
However, writing doesn't work:
for(auto&& i : boost::join(boost::join(a,b),c)) {
i += 1; // Fails :(
std::cout << i;
}
My variadic join has the same problem, i.e. works for reading but not for writing:
template<class C> C&& join(C&& c) { return c; }
template<class C, class D, class... Args>
auto join(C&& c, D&& d, Args&&... args)
-> decltype(boost::join(boost::join(std::forward<C>(c), std::forward<D>(d)),
join(std::forward<Args>(args)...))) {
return boost::join(boost::join(std::forward<C>(c), std::forward<D>(d)),
join(std::forward<Args>(args)...));
}
Mehrdad gave the solution in the comments
template<class C>
auto join(C&& c)
-> decltype(boost::make_iterator_range(std::begin(c),std::end(c))) {
return boost::make_iterator_range(std::begin(c),std::end(c));
}
template<class C, class D, class... Args>
auto join(C&& c, D&& d, Args&&... args)
-> decltype(boost::join(boost::join(boost::make_iterator_range(std::begin(c),std::end(c)),
boost::make_iterator_range(std::begin(d),std::end(d))),
join(std::forward<Args>(args)...))) {
return boost::join(boost::join(boost::make_iterator_range(std::begin(c),std::end(c)),
boost::make_iterator_range(std::begin(d),std::end(d))),
join(std::forward<Args>(args)...));
}
There are two overloads of boost::join
template<typename SinglePassRange1, typename SinglePassRange2>
joined_range<const SinglePassRange1, const SinglePassRange2>
join(const SinglePassRange1& rng1, const SinglePassRange2& rng2)
template<typename SinglePassRange1, typename SinglePassRange2>
joined_range<SinglePassRange1, SinglePassRange2>
join(SinglePassRange1& rng1, SinglePassRange2& rng2);
When you do this
for(auto&& i : boost::join(boost::join(a,b), c)) {
// ^^^^ ^^^^ temporary here
// ||
// calls the const ref overload
You get a temporary joined_range and as those can only bind to const references, the first overload is selected which returns a range that doesn't allow modifying.
You can work around this if you avoid temporaries:
#include <boost/range.hpp>
#include <boost/range/join.hpp>
int main()
{
std::vector<int> a = {1,2,3}, b = {4,5,6}, c = {7,8,9};
auto range = boost::join(a,b);
for(int& i : boost::join(range,c)) {
i += 1;
std::cout << i;
}
}
Live demo.
I haven't looked into your variadic functions, but the problem is likely similar.
Here's a complete solution, which works correctly on GCC 12. For GCC 10 & 11, the subranges function can be used to obtain an array of subranges, which can then be used as the lhs argument to | std::views::join.
EDIT: These functions only return on ranges that have a common iterator type. If you don't have a common iterator type, one option is to create a new container from the ranges (which is probably not what you want), or to create a custom type with different sub-ranges (which can't be used with std::views::join).
#include <ranges>
#include <vector>
#include <iostream>
#include <tuple>
#include <array>
#include <algorithm>
namespace detail {
template<std::size_t N, typename... Ts>
struct has_common_type_helper {
using T1 = std::decay_t<std::tuple_element_t<N-1, std::tuple<Ts...>>>;
using T2 = std::decay_t<std::tuple_element_t<N-2, std::tuple<Ts...>>>;
static constexpr bool value = std::same_as<T1, T2> && has_common_type_helper<N-1, Ts...>::value;
};
template<typename... Ts>
struct has_common_type_helper<0, Ts...> : std::false_type {
static_assert(std::is_void_v<Ts...>, "Undefined for an empty parameter pack");
};
template<typename... Ts>
struct has_common_type_helper<1, Ts...> : std::true_type {};
template<typename T> struct iterator_types;
template<std::ranges::range... Ts>
struct iterator_types<std::tuple<Ts...>> {
using type = std::tuple<std::ranges::iterator_t<Ts>...>;
};
}
template<typename T>
struct has_common_type;
template<typename T1, typename T2>
struct has_common_type<std::pair<T1,T2>> {
static constexpr bool value = std::same_as<std::decay_t<T1>, std::decay_t<T2>>;
};
template <typename... Ts>
struct has_common_type<std::tuple<Ts...>> : detail::has_common_type_helper<sizeof...(Ts), Ts...> {};
template <typename T>
inline constexpr bool has_common_type_v = has_common_type<T>::value;
template<std::size_t I = 0, typename Array, typename... Ts, typename Func> requires (I == sizeof...(Ts))
void init_array_from_tuple(Array& a, const std::tuple<Ts...>& t, Func fn)
{
}
template<std::size_t I = 0, typename Array, typename... Ts, typename Func> requires (I < sizeof...(Ts))
void init_array_from_tuple(Array& a, const std::tuple<Ts...>& t, Func fn)
{
a[I] = fn(std::get<I>(t));
init_array_from_tuple<I+1>(a, t, fn);
}
template<std::ranges::range... Ranges>
auto subranges(Ranges&&... rngs)
{
using IteratorTypes = detail::iterator_types<std::tuple<Ranges...>>::type;
static_assert(has_common_type_v<IteratorTypes>);
using SubrangeT = std::ranges::subrange<std::tuple_element_t<0, IteratorTypes>>;
auto subrngs = std::array<SubrangeT, sizeof...(Ranges)>{};
auto t = std::tuple<Ranges&&...>{std::forward<Ranges>(rngs)...};
auto fn = [](auto&& rng) {
return std::ranges::subrange{rng.begin(), rng.end()};
};
init_array_from_tuple(subrngs, t, fn);
return subrngs;
}
#if __GNUC__ >= 12
template<std::ranges::range... Ranges>
auto join(Ranges&&... rngs)
{
return std::ranges::owning_view{subranges(std::forward<Ranges>(rngs)...) | std::views::join};
}
#endif
int main()
{
std::vector<int> v1{1,2,3};
std::vector<int> v2{4};
std::vector<int> v3{5,6};
#if __GNUC__ >= 12
std::ranges::copy(join(v1,v2,v3,v1), std::ostream_iterator<int>(std::cout, " "));
#else
auto subrngs = subranges(v1,v2,v3,v1);
std::ranges::copy(subrngs | std::views::join, std::ostream_iterator<int>(std::cout, " "));
#endif
std::cout << '\n';
return 0;
}
Here's an implementation that works for two different ranges with a common reference type. You can extend it to 3 ranges using a brute-force approach.
#include <ranges>
#include <vector>
#include <list>
#include <iostream>
#include <algorithm>
template<std::ranges::range Range1, std::ranges::range Range2>
auto join2(Range1&& rng1, Range2&& rng2)
{
using Ref1 = std::ranges::range_reference_t<Range1>;
using Ref2 = std::ranges::range_reference_t<Range2>;
using Ref = std::common_reference_t<Ref1, Ref2>;
class Iter {
public:
using value_type = std::remove_cv_t<std::remove_reference_t<Ref>>;
using difference_type = std::ptrdiff_t;
Iter() = default;
Iter(Range1&& rng1_, Range2&& rng2_, bool begin)
: m_it1{begin ? rng1_.begin() : rng1_.end()}
, m_it2{begin ? rng2_.begin() : rng2_.end()}
, m_e1{rng1_.end()} {}
bool operator==(const Iter& rhs) const {
return m_it1 == rhs.m_it1 && m_it2 == rhs.m_it2;
}
Ref operator*() const {
return m_it1 != m_e1 ? *m_it1 : *m_it2;
}
Iter& operator++() {
(m_it1 != m_e1) ? (void)++m_it1 : (void)++m_it2;
return *this;
}
Iter operator++(int) {
Iter ret = *this;
++(*this);
return ret;
}
private:
std::ranges::iterator_t<Range1> m_it1;
std::ranges::iterator_t<Range2> m_it2;
std::ranges::iterator_t<Range1> m_e1;
};
static_assert(std::forward_iterator<Iter>);
auto b = Iter{std::forward<Range1>(rng1), std::forward<Range2>(rng2), true};
auto e = Iter{std::forward<Range1>(rng1), std::forward<Range2>(rng2), false};
return std::ranges::subrange<Iter>{b, e};
}
int main()
{
std::vector<int> v{1,2,3};
std::list<int> l{4,5,6};
std::ranges::copy(join2(v,l), std::ostream_iterator<int>(std::cout, " "));
std::cout << '\n';
return 0;
}
P.S. I am not optimistic about a variadic implementation, although I'm sure someone smarter than me would be able to figure it out.