I have large enum for example:
enum { elem0, elem1, elem2, elem3 ...... elem1000 }
I would like to create an array of float numbers using just some of the enum elements.
For example, I would like to have only three elements in array: elem0, elem7, elem999 and I would like to acccess them in similar style as this:
array[elem0]=123
array[elem7]=12.5
array[elem999]=15.6
What would be the most elegant way to implement such an array so that array is going to have only three elements ?
Just write a conversion from enum to array index:
int EnumToIdx(Elem elem)
{
switch (elem)
{
case elem0: return 0;
case elem7: return 1;
case elem999: return 2;
}
throw std::invalid_argument("EnumToIdx: no conversion"); // or whatever
}
Usage
array[EnumToIdx(elem0)] = 123;
array[EnumToIdx(elem7)] = 12.5;
array[EnumToIdx(elem999)] = 15.6;
Maybe overkill, but then again maybe not - template expansion can write conversion functions for you.
This template class allows you to specify which elems should be valid for the vector, and in which order.
It also provides the ability to index into the vector by elem, either dynamically (throw exception if does not exists) or statically (fail to compile if out of bounds).
#include <array>
#include <iostream>
#include <algorithm>
#include <iterator>
#include <stdexcept>
//
// boilerplate
//
namespace notstd {
template<class T, class Tuple>
struct tuple_index;
template<class T, class... Types>
struct tuple_index<T, std::tuple<T, Types...>> {
using type = std::size_t;
static const type value = 0;
};
template<class T, class U, class... Types>
struct tuple_index<T, std::tuple<U, Types...>> {
using type = std::size_t;
static const type value = 1 + tuple_index<T, std::tuple<Types...>>::value;
};
}
enum element_type {
elem0, elem1, elem2, elem3, elem4, elem5, elem1000
};
template<element_type Value>
struct where_type
{
static const element_type value = Value;
};
template<element_type Value>
static constexpr auto where = where_type<Value> {};
template<element_type...Permitted>
struct restricted_vector : private std::array<double, sizeof...(Permitted)> {
using corresponding_tuple = std::tuple<where_type<Permitted>...>;
using inherited = std::array<double, sizeof...(Permitted)>;
using inherited::inherited;
static auto conversion(element_type e) -> std::size_t
{
static const std::array<std::pair<element_type, std::size_t>, sizeof...(Permitted)> a = {
std::make_pair(Permitted, notstd::tuple_index<where_type<Permitted>, corresponding_tuple>::value)...
};
auto ifind = std::find_if(a.begin(), a.end(), [e](auto&& elem) { return elem.first == e; });
if (ifind == a.end()) {
throw std::out_of_range("invalid element");
}
return ifind->second;
}
template<element_type Elem>
auto& operator[](where_type<Elem>) {
auto pos = notstd::tuple_index<where_type<Elem>, corresponding_tuple >::value;
return inherited::operator[](pos);
}
template<element_type Elem>
auto const& operator[](where_type<Elem>) const {
auto pos = notstd::tuple_index<where_type<Elem>, corresponding_tuple >::value;
return inherited::operator[](pos);
}
// dynamic access
auto& at(element_type e) {
return inherited::operator[](conversion(e));
}
auto const& at(element_type e) const {
return inherited::operator[](conversion(e));
}
using inherited::begin;
using inherited::end;
using inherited::size; // etc
};
int main() {
auto v1 = restricted_vector<elem3, elem4, elem5> {};
v1[where<elem4>] = 0.4;
v1[where<elem5>] = 0.5;
v1[where<elem3>] = 0.3;
std::copy(v1.begin(), v1.end(), std::ostream_iterator<double>(std::cout, ", "));
std::cout << "\n";
std::cout << "at elem4: " << v1.at(elem4) << std::endl;
}
expected output:
0.3, 0.4, 0.5,
at elem4: 0.4
Related
I would like to access to a tuple element at compile time by a value constexpr in the type
#include <iostream>
#include <tuple>
#include <utility>
struct A {
static constexpr int id = 1;
void work() {
std::cout << "A" << std::endl;
}
};
struct B {
static constexpr int id = 2;
void work() {
std::cout << "B" << std::endl;
}
};
int main() {
A a;
B b;
std::tuple<A,B> t = std::make_tuple(a,b);
static constexpr int search_id = 2;
auto& item = std::get< ? ( T::id == search_id ) ? >(t);
item.work();
return 0;
}
I guess using std::apply and test would be a runtime search...
I'm using c++20
Instead of std::get a single element, you can use std::apply to iterate over the elements of the tuple and perform operations based on the element type
A a;
B b;
auto t = std::make_tuple(a, b);
static constexpr int search_id = 2;
std::apply([](auto&... items) {
([]<class T>(T& item) {
if constexpr (T::id == search_id)
item.work();
}(items), ...);
}, t);
Demo
If you really want to get a single tuple element with a specific id value, you can still use std::apply to expand the id of all elements and find the offset of the value equal to search_id as the template parameter of std::get
auto& item = std::apply([&t]<class... Args>(const Args&... items) -> auto& {
constexpr auto id = [] {
std::array ids{Args::id...};
return ids.end() - std::ranges::find(ids, search_id);
}();
return std::get<id>(t);
}, t);
item.work();
You can create constrexpr function to get index:
template <typename... Ts>
constexpr std::size_t get_index(int id)
{
constexpr int ids[] = {Ts::id...};
const auto it = std::find(std::begin(ids), std::end(ids), id);
// Handle absent id.
if (it == std::end(ids)) {
throw std::runtime("Invalid id");
}
// You can also possibly handle duplicate ids.
return std::distance(std::begin(ids), it);
}
template <int id, typename... Ts>
constexpr auto& get_item(std::tuple<Ts...>& t)
{
return std::get<get_index<Ts...>(id)>(t);
}
template <int id, typename... Ts>
constexpr const auto& get_item(const std::tuple<Ts...>& t)
{
return std::get<get_index<Ts...>(id)>(t);
}
and then
auto& item = get_item<search_id>(t);
This is a prime candidate for std::disjunction, which can be used to perform a compile-time linear search; you just need a helper type to act as the predicate:
namespace detail {
template<typename T, auto Id, auto I, typename U = std::tuple_element_t<I, T>>
struct get_by_id_pred : std::bool_constant<std::remove_cvref_t<U>::id == Id> {
static constexpr auto index = I;
};
}
template<int Id>
constexpr auto&& get_by_id(auto&& t) noexcept {
using tuple_t = std::remove_cvref_t<decltype(t)>;
return [&]<auto ...Is>(std::index_sequence<Is...>) -> auto&& {
using res = std::disjunction<detail::get_by_id_pred<tuple_t, Id, Is>...>;
static_assert(res::value, "id not found");
return std::get<res::index>(decltype(t)(t));
}(std::make_index_sequence<std::tuple_size_v<tuple_t>>{});
}
...
auto& item = get_by_id<search_id>(t);
Online Demo
Godbolt link: https://godbolt.org/z/18nseEn4G
I have a std::map of various types of vectors (cast to void*) and a T& get<T> method that gives me a reference to an element in one of the vectors in the map.
class Container {
public:
Container() {
auto v1 = new std::vector<int>({1, 2, 3, 4, 5});
auto v2 = new std::vector<char>({'a','b','c','d','e'});
auto v3 = new std::vector<double>({1.12, 2.34, 3.134, 4.51, 5.101});
items.insert({
std::type_index(typeid(std::vector<int>)),
reinterpret_cast<void*>(v1)
});
items.insert({
std::type_index(typeid(std::vector<char>)),
reinterpret_cast<void*>(v2)
});
items.insert({
std::type_index(typeid(std::vector<double>)),
reinterpret_cast<void*>(v3)
});
}
template<typename T>
T& get(int index) {
auto idx = std::type_index(typeid(std::vector<T>));
auto ptr = items.at(idx);
auto vec = reinterpret_cast<std::vector<T>*>(ptr);
return (*vec)[index];
}
private:
std::map<std::type_index, void*> items {};
};
I want to be able to use structured binding to get back references to 3 elements all at the same index but in difference vectors, but I'm not sure how to create a tuple with multiple calls to the T& get<T> method.
Something like this;
auto [a, b, c] = myContainer.get_all<int, char, double>(1); // get a reference to an int, a char, and a double from myContainer at index 1.
I'm currently trying to make use of repeated calls to T& get<T> for each parameter in a parameter pack, but I can't figure out the correct syntax.
template<typename... Ts>
auto get_all(int index) {
return std::tuple_cat<Ts...>(
std::make_tuple<Ts>(get<Ts>(index)...)
);
How could I make this work?
Here is a link to my current attempt:
https://godbolt.org/z/18nseEn4G
Alternatively, is there a "better way" to achieve this?
I would suggest using type erasure. Here is an example:
#include <vector>
#include <typeindex>
#include <memory>
#include <any>
#include <unordered_map>
#include <iostream>
#include <experimental/propagate_const>
// If no library implementation is availble, one may be copied from libstdc++
template<class T>
using propagate_const = std::experimental::propagate_const<T>;
class Container
{
public:
Container() {
std::unique_ptr<Eraser> v1{ static_cast<Eraser*>(new ErasedVector<int>(1, 2, 3, 4, 5)) };
std::unique_ptr<Eraser> v2{ static_cast<Eraser*>(new ErasedVector<char>('a','b','c','d','e')) };
std::unique_ptr<Eraser> v3{ static_cast<Eraser*>(new ErasedVector<double>(1.12, 2.34, 3.134, 4.51, 5.101)) };
items[std::type_index(typeid(int))] = std::move(v1);
items[std::type_index(typeid(char))] = std::move(v2);
items[std::type_index(typeid(double))] = std::move(v3);
}
template<typename... Ts>
std::tuple<Ts&...> get(size_t index)
{
return {
std::any_cast<std::reference_wrapper<Ts>>((*items.find(std::type_index{typeid(Ts)})->second)[index]).get()...
};
}
template<typename... Ts, typename = std::enable_if_t<(std::is_const_v<Ts> && ...)>>
std::tuple<Ts&...> get(size_t index) const
{
return {
std::any_cast<std::reference_wrapper<Ts>>((*items.find(std::type_index{typeid(Ts)})->second)[index]).get()...
};
}
private:
class Eraser
{
public:
virtual std::any operator[](size_t index) = 0;
virtual std::any operator[](size_t index) const = 0;
virtual ~Eraser() = default;
};
template <typename T>
class ErasedVector : public Eraser
{
public:
template <typename... Args>
ErasedVector(Args&&... args) :
data{ std::forward<Args>(args)... }
{
}
virtual std::any operator[](size_t index) override final
{
return std::reference_wrapper{ data[index] };
};
virtual std::any operator[](size_t index) const override final
{
return std::reference_wrapper{ data[index] };
}
private:
std::vector<T> data;
};
std::unordered_map<std::type_index, propagate_const<std::unique_ptr<Eraser>>> items;
};
It works properly on this example:
int main()
{
Container co;
auto [i0_0, c0_0, d0_0] = co.get<int, char, double>(0);
std::cout << i0_0 << ' ' << c0_0 << ' ' << d0_0 << '\n';
i0_0 = 3; // is a reference
d0_0 = 42; // is a reference
auto [i0_1, d0_1] = static_cast<const Container&>(co).get<const int, const double>(0); // works on const Container
std::cout << i0_1 << ' ' << d0_1; // original values modified
// i0_1 = 0xDEADBEEF; can be const too
}
And outputs:
1 a 1.12
3 42
Demo
Simply:
template<typename... Ts>
auto get_all(int index) {
return std::tuple<Ts&...>(get<Ts>(index)...);
}
Demo
You can use std::tie to take all of the "returns" from get<Ts>(index) can pack them into a tuple of references. That would look like
template<typename... Ts>
auto get_all(int index) {
return std::tie(get<Ts>(index)...);
}
i am trying to convert c multidimensional array to multidimensional c++ vector, i mean, to convert something like this int arr[2][3] = {{1,2,3}, {4,5,6}}; into the correspondent vector.
The array isn't necessarily 2D shaped, it could also be something like this:
int arr[2][2][3] = {
{
{1,2,3},
{4,5,6},
},
{
{7,8,9},
{10,11,12},
}
};
initially i thought that something like this would have worked, but it didn't really turned out to be the case because it seems like if std::vector doesn't allows conversions from C arrays.
std::vector<std::any> V(arr);
Then i thought at something like function recursion, this is my attempt, that (i don't know why!) throws error: no matching function for call to 'length' .
#include <iostream>
#include <type_traits>
#include <vector>
#include <any>
// Get the lenght of a classic C array.
template <class T, unsigned S>
inline unsigned length(const T (&v)[S]) {
return S;
};
// Check wether the input is a classic C array or not.
template <class T>
bool is(const T& t) {
return std::is_array_v<T>;
};
// Turn the classic C input array to vector.
template <class T>
std::vector<std::any> toVector(const T& t) {
std::vector<std::any> V;
for (int k = 0; k < length(t); k++) {
if (is(t[k])) {
V.push_back(toVector(t[k]));
} else {
V.push_back(t[k]);
}
}
return V;
}
int main() {
int16 a[] = {1,2,3};
auto b = toVector(a);
}
What did i wrong in the second attempt? Alternatively, is there a simpler way to manage to do this?
Also, i think it would be better to convert all the numbers in the vector to a unique given data type, is this possible?
I am using c++11 and g++ as compiler –
Note that i do not know how many dimensions my array has.
The equivalent of a C multidimensional array is a flat C++ vector plus a mumtidimensional array view.
The naive equivalent is a vector of vectors (maybe of vectors), which actually corresponds to a C "jagged" array. The memory layout and performance characteristics will be very different.
There are many multi dimensional array-view implementstions on the web.
template<class A>
struct raw_ptr { using type=A*; };
template<class A, std::size_t N>
struct raw_ptr<A[N]>:raw_ptr<A>{};
template<class A>
using raw_ptr_t = typename raw_ptr<A>::type;
template<class T>
struct array_view;
template<class T, std::size_t D0, std::size_t D1>
struct array_view<T[D0][D1]> {
T(*data)[D1]=0;
constexpr array_view( T(&arr)[D0][D1] ):data(arr) {}
explicit array_view( raw_ptr_t<T> buff ):data(reinterpret_cast<T(*)[D1]>(buff)) {}
constexpr array_view<T[D1]> operator[](std::size_t i)const{
return data[i];
}
};
template<class T, std::size_t D0>
struct array_view<T[D0]> {
constexpr array_view( T(&arr)[D0] ):data(arr) {}
explicit constexpr array_view( T* buff ):data(buff) {}
T* data=0;
constexpr T& operator[](std::size_t i)const{
return data[i];
}
};
to convert a int[4][5][6] you'd do:
int array[4][5][6]={/*whatever*/};
std::vector<int> buff(&array[0][0][0], &array[3][4][5]);
array_view<int[4][5][6]> view{buff.data()};
now, view[a][b][c] is the same as array[a][b][c].
Live example.
template<class Array>
struct wrap_array_in_vector {
using raw_ptr = raw_ptr_t<Array>;
using value_type = std::remove_pointer_t<raw_ptr>;
std::vector<value_type> data;
array_view<Array> view;
wrap_array_in_vector(wrap_array_in_vector const& other):
data(other.data),
view(data.data())
{}
wrap_array_in_vector(wrap_array_in_vector && other):
data(std::move(other.data)),
view(data.data())
{
other.view.data = nullptr; // no longer valid
}
decltype(auto) operator[](std::size_t i)const {
return view[i];
}
wrap_array_in_vector( Array const& arr ):
data( reinterpret_cast<value_type const*>(&arr[0]), reinterpret_cast<value_type const*>(&arr[1]) ),
view(data.data())
{}
};
template<class Array>
wrap_array_in_vector(Array&)->wrap_array_in_vector<Array>;
template<class Array>
wrap_array_in_vector(Array const&)->wrap_array_in_vector<Array>;
this lets you do
wrap_array_in_vector wrapped = array;
and wrapped deduces all of its type information it needs.
This should be close to optimal for conversion to a vector. The part of the description that was misleading was that the sizes are not regular, as they are list-initialized and in this case with int, will be zeros.
#include <type_traits>
#include <vector>
#include <iostream>
template<typename T, std::size_t N>
auto to_vectors( T const (&c_array)[N] ) {
using element_base_type = std::decay_t<T>;
if constexpr( std::is_array_v<T> ) {
using child_t = std::remove_cv_t<std::remove_reference_t<decltype( to_vectors( c_array[0] ) )>>;
auto result = std::vector<child_t>( );
result.reserve( N );
for( auto const & element: c_array ) {
result.push_back( to_vectors( element ) );
}
return result;
} else {
return std::vector<T>( c_array, c_array + N );
}
}
int arr[2][2][3] = {
{
{1,2,3},
{4,5,6},
},
{
{7,8,9},
}
};
auto v0 = to_vectors( arr );
template<typename Vec>
void display( Vec const & vec ) {
if constexpr( std::is_same_v<int, typename Vec::value_type> ) {
std::cout << "elements: {";
for( int e: vec ) {
std::cout << e << ',';
}
std::cout << "}\n";
} else {
std::cout << "element count: " << vec.size( ) << '\n';
for( auto const & child: vec ) {
display( child );
}
}
}
int main( ) {
display( v0 );
}
this will output
element count: 2
element count: 2
elements: {1,2,3,}
elements: {4,5,6,}
element count: 2
elements: {7,8,9,}
elements: {0,0,0,}
I have been writing a set of classes to allow for a simple python-like zip-function. The following snippet works (almost) just as expected. However, the two variables a and b are not const.
std::vector<double> v1{0.0, 1.1, 2.2, 3.3};
std::vector<int> v2{0, 1, 2};
for (auto const& [a, b] : zip(v1, v2))
{
std::cout << a << '\t' << b << std::endl;
a = 3; // I expected this to give a compiler error, but it does not
std::cout << a << '\t' << b << std::endl;
}
I have been using gcc 7.3.0.
Here is the MCVE:
#include <iostream>
#include <tuple>
#include <vector>
template <class ... Ts>
class zip_iterator
{
using value_iterator_type = std::tuple<decltype( std::begin(std::declval<Ts>()))...>;
using value_type = std::tuple<decltype(*std::begin(std::declval<Ts>()))...>;
using Indices = std::make_index_sequence<sizeof...(Ts)>;
value_iterator_type i;
template <std::size_t ... I>
value_type dereference(std::index_sequence<I...>)
{
return value_type{*std::get<I>(i) ...};
}
public:
zip_iterator(value_iterator_type it) : i(it) {}
value_type operator*()
{
return dereference(Indices{});
}
};
template <class ... Ts>
class zipper
{
using Indices = std::make_index_sequence<sizeof...(Ts)>;
std::tuple<Ts& ...> values;
template <std::size_t ... I>
zip_iterator<Ts& ...> beginner(std::index_sequence<I...>)
{
return std::make_tuple(std::begin(std::get<I>(values)) ...);
}
public:
zipper(Ts& ... args) : values{args...} {}
zip_iterator<Ts& ...> begin()
{
return beginner(Indices{});
}
};
template <class ... Ts>
zipper<Ts& ...> zip(Ts& ... args)
{
return {args...};
}
int main()
{
std::vector<double> v{1};
auto const& [a] = *zip(v).begin();
std::cout << a << std::endl;
a = 2; // I expected this to give a compiler error, but it does not
std::cout << a << std::endl;
}
You have a tuple of a reference, which means that the reference itself will be const qualified (which is ill-formed but in this context ignored), not the value referenced by it.
int a = 7;
std::tuple<int&> tuple = a;
const auto&[aa] = tuple;
aa = 9; // ok
If you look how std::get is defined, you'll see that it returns const std::tuple_element<0, std::tuple<int&>>& for the structured binding above. As the first tuple element is a reference, the const& has no effect, and thus you can modify the return value.
Really, it's same thing if you have a class pointer/reference member that you can modify in a const qualified member function (the value pointed/referenced that is).
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.