constexpr to concatenate two or more char strings - c++

I'm trying to make a constexpr function that will concatenate an arbitrary number of char arrays by working from the following answer by Xeo, which concatenates two char arrays.
https://stackoverflow.com/a/13294458/1128289
#include <array>
template<unsigned... Is> struct seq{};
template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};
template<unsigned... Is>
struct gen_seq<0, Is...> : seq<Is...>{};
template<unsigned N1, unsigned... I1, unsigned N2, unsigned... I2>
constexpr std::array<char const, N1+N2-1> concat(char const (&a1)[N1], char const (&a2)[N2], seq<I1...>, seq<I2...>){
return {{ a1[I1]..., a2[I2]... }};
}
template<unsigned N1, unsigned N2>
constexpr std::array<char const, N1+N2-1> concat(char const (&a1)[N1], char const (&a2)[N2]){
return concat(a1, a2, gen_seq<N1-1>{}, gen_seq<N2>{});
}
My attempt thus far:
#include <iostream>
#include <array>
template<unsigned... Is> struct seq{};
template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};
template<unsigned... Is>
struct gen_seq<0, Is...> : seq<Is...>{};
template<unsigned N1, unsigned... I1, unsigned N2, unsigned... I2>
constexpr const std::array<char, N1+N2-1>
concat_impl(
const char (&a1)[N1], const char (&a2)[N2], seq<I1...>, seq<I2...>)
{
return {{ a1[I1]..., a2[I2]... }};
}
template<unsigned N1, unsigned N2>
constexpr const std::array<char, N1+N2-1>
concat(const char (&a1)[N1], const char (&a2)[N2])
{
return concat_impl(a1, a2, gen_seq<N1-1>{}, gen_seq<N2>{});
}
template<unsigned N1, unsigned N2, class... Us>
constexpr auto
concat(const char(&a1)[N1], const char(&a2)[N2], const Us&... xs)
-> std::array<char, N1 + decltype(concat(a2, xs...))::size() - 1>
{
return concat(a1, concat(a2, xs...));
}
int main()
{
auto const s = concat("hi ", "there!");
std::cout << s.data() << std::endl;
// compile error:
auto const t = concat("hi ", "there ", "how ", "are ", "you?");
std::cout << t.data() << std::endl;
}
Both gcc 4.9 and clang 3.5 give errors indicating that no function matching the concat inside the decltype expression can be found.
clang:
error: no matching function for call to 'concat'
auto const t = concat("hi ", "there ", "how ", "are ", "you?");
^~~~~~
ctconcat.cpp:105:16: note: candidate template ignored: substitution failure [with N1 = 4, N2 = 7, Us = <char [5], char [5], char [5]>]: no matching function for call to 'concat'
constexpr auto concat(const char(&a1)[N1], const char(&a2)[N2], const Us&... xs) -> std::array<char, N1 + decltype(concat(a2, xs...))::size() - 1>
^ ~~~~~~
ctconcat.cpp:62:43: note: candidate function template not viable: requires 2 arguments, but 5 were provided
constexpr const std::array<char, N1+N2-1> concat(const char (&a1)[N1], const char (&a2)[N2])
^
1 error generated.
The errors from gcc and clang both indicate that the second concat function template is not a candidate for the concat in the decltype expression. Only the first template is considered. Why is that and how do I fix this?
Edit: Relevant question on why decltype can't be used recursively
trailing return type using decltype with a variadic template function

template<size_t S>
using size=std::integral_constant<size_t, S>;
template<class T, size_t N>
constexpr size<N> length( T const(&)[N] ) { return {}; }
template<class T, size_t N>
constexpr size<N> length( std::array<T, N> const& ) { return {}; }
template<class T>
using length_t = decltype(length(std::declval<T>()));
constexpr size_t sum_string_sizes() { return 0; }
template<class...Ts>
constexpr size_t sum_string_sizes( size_t i, Ts... ts ) {
return (i?i-1:0) + sum_sizes(ts...);
}
then
template
template<unsigned N1, unsigned N2, class... Us>
constexpr auto
concat(const char(&a1)[N1], const char(&a2)[N2], const Us&... xs)
-> std::array<char, sum_string_sizes( N1, N2, length_t<Us>::value... )+1 >
{
return concat(a1, concat(a2, xs...));
}
which gets rid of the recursion-in-decltype.
Here is a full example using the above approach:
template<size_t S>
using size=std::integral_constant<size_t, S>;
template<class T, size_t N>
constexpr size<N> length( T const(&)[N] ) { return {}; }
template<class T, size_t N>
constexpr size<N> length( std::array<T, N> const& ) { return {}; }
template<class T>
using length_t = decltype(length(std::declval<T>()));
constexpr size_t string_size() { return 0; }
template<class...Ts>
constexpr size_t string_size( size_t i, Ts... ts ) {
return (i?i-1:0) + string_size(ts...);
}
template<class...Ts>
using string_length=size< string_size( length_t<Ts>{}... )>;
template<class...Ts>
using combined_string = std::array<char, string_length<Ts...>{}+1>;
template<class Lhs, class Rhs, unsigned...I1, unsigned...I2>
constexpr const combined_string<Lhs,Rhs>
concat_impl( Lhs const& lhs, Rhs const& rhs, seq<I1...>, seq<I2...>)
{
// the '\0' adds to symmetry:
return {{ lhs[I1]..., rhs[I2]..., '\0' }};
}
template<class Lhs, class Rhs>
constexpr const combined_string<Lhs,Rhs>
concat(Lhs const& lhs, Rhs const& rhs)
{
return concat_impl(
lhs, rhs,
gen_seq<string_length<Lhs>{}>{},
gen_seq<string_length<Rhs>{}>{}
);
}
template<class T0, class T1, class... Ts>
constexpr const combined_string<T0, T1, Ts...>
concat(T0 const&t0, T1 const&t1, Ts const&...ts)
{
return concat(t0, concat(t1, ts...));
}
template<class T>
constexpr const combined_string<T>
concat(T const&t) {
return concat(t, "");
}
constexpr const combined_string<>
concat() {
return concat("");
}
live example

With C++17 the solution becomes very simple (here's the live version):
#include <initializer_list>
// we cannot return a char array from a function, therefore we need a wrapper
template <unsigned N>
struct String {
char c[N];
};
template<unsigned ...Len>
constexpr auto cat(const char (&...strings)[Len]) {
constexpr unsigned N = (... + Len) - sizeof...(Len);
String<N + 1> result = {};
result.c[N] = '\0';
char* dst = result.c;
for (const char* src : {strings...}) {
for (; *src != '\0'; src++, dst++) {
*dst = *src;
}
}
return result;
}
// can be used to build other constexpr functions
template<unsigned L>
constexpr auto makeCopyright(const char (&author)[L]) {
return cat("\xC2\xA9 ", author);
}
constexpr char one[] = "The desert was the apotheosis of all deserts";
constexpr char two[] = "huge, standing to the sky";
constexpr auto three = cat(
cat(one, ", ", two).c, // can concatenate recursively
" ",
"for what looked like eternity in all directions."); // can use in-place literals
constexpr auto phrase = cat(
three.c, // can reuse existing cats
"\n",
makeCopyright("Stephen King").c);
#include <cstdio>
int main() {
puts(phrase.c);
return 0;
}

Related

How to generate a look-up table in compile time in C++?

I have been attempting to implement a compiler generated look-up table
containing the values of the sine function. The C++ code looks like this
#include <cstdlib>
#include <cmath>
#include <array>
#include <iostream>
using namespace std;
template<typename T>
constexpr T look_up_table_elem(int i)
{
return {};
}
template<>
constexpr float look_up_table_elem(int i)
{
return sin(static_cast<float>(i)*2*3.14/64);
}
template<typename T, int... N>
struct lookup_table_expand{};
template<typename T, int... N>
struct lookup_table_expand<T, 1, N...>
{
static constexpr std::array<T, sizeof...(N) + 1> values =
{{look_up_table_elem<T>(0), N...}};
};
template<typename T, int L, int... N> struct lookup_table_expand<T, L, N...>
: lookup_table_expand<T, L-1, look_up_table_elem<T>(L-1), N...> {};
template<typename T, int... N>
constexpr std::array<T, sizeof...(N) + 1> lookup_table_expand<T, 1, N...>::values;
const std::array<float, 65> lookup_table = lookup_table_expand<float, 65>::values;
int main(int argc, char** argv) {
for(const float &item : lookup_table){
std::cout << "Sin: " << item << std::endl;
}
return 0;
}
I have been struggling with the compilation process.
main.cpp: In instantiation of 'struct lookup_table_expand<float, 65>':
main.cpp:49:74: required from here
main.cpp:44:52: error: conversion from 'float' to 'int' in a converted constant expression
44 | : lookup_table_expand<T, L-1, look_up_table_elem<T>(L-1), N...> {};
| ~~~~~~~~~~~~~~~~~~~~~^~~~~
main.cpp:44:52: error: could not convert 'look_up_table_elem<float>((65 - 1))' from 'float' to 'int'
main.cpp:49:76: error: 'values' is not a member of 'lookup_table_expand<float, 65>'
49 | const std::array<float, 65> lookup_table = lookup_table_expand<float, 65>::values;
| ^~~~~~
Can anybody tell me what I am doing wrong?
The code may be greatly simplified to generate the LUT:
template <std::size_t N, typename F = std::identity>
constexpr auto gen_float_array(const F& f = F{})
{
std::array<float, N> arr;
for (std::size_t i = 0; i < N; ++i)
arr[i] = f(static_cast<float>(i));
return arr;
}
It may be used as follows:
constexpr auto lookup_map =
gen_float_array<32>([](auto f) {
return f * 3.14f; // here could a call be placed to a constexpr sin function
});
Example: https://godbolt.org/z/ssEhK6bd7
As Markus Mayr pointed out, you are still in need of a constexpr sin function to get the desired results for your use case.
I do not really understand what you are trying to do here, but the error message indicates that look_up_table_elem returns a float and that you are feeding it into an int ... parameter pack at the following lines:
template<typename T, int L, int... N> struct lookup_table_expand<T, L, N...>
: lookup_table_expand<T, L-1, look_up_table_elem<T>(L-1), N...> {};
By the way, this is how I would implement a function like this:
constexpr float lookup_table_elem(std::size_t i, std::size_t n)
{
return static_cast<float>(i) / static_cast<float>(n); // Have a constexpr sin function here!
}
template <class T>
struct lookup_table_impl;
template <std::size_t... I>
struct lookup_table_impl<std::index_sequence<I...>>
{
static constexpr std::size_t N = sizeof...(I);
static constexpr std::array<float, N> values{ lookup_table_elem(I, N) ... };
};
template <std::size_t N>
using lookup_table = lookup_table_impl<std::make_index_sequence<N>>;
template <std::size_t N>
constexpr auto lookup_table_values = lookup_table<N>::values;
Please note that std::sin is not a constexpr function (yet?). You would have to write your own compile-time approximation here.
And as #HolyBlackCat suggested in a comment below, the following, very simple solution is also possible with modern C++ (>= 17, I think):
template <std::size_t N>
constexpr std::array<float, N> make_lookup_table()
{
std::array<float, N> v;
for (std::size_t i = 0u; i < N; ++i)
{
v[i] = static_cast<float>(i); // Insert constexpr sin function here!
}
return v;
}
template <std::size_t N>
constexpr auto lookup_table_values = make_lookup_table<N>();

Make C++14 constexpr function C++11 compatible

I have written a class multi_array which is sort of an extension of std::array to multiple dimensions.
template <typename T, std::size_t... N>
class multi_array {
template <std::size_t... I, typename... Idx>
constexpr std::size_t linearized_index(meta::index_sequence<I...>,
Idx... idx) const {
std::size_t index = 0;
using unpack = std::size_t[];
(void)unpack{0UL,
((void)(index = (index + unpack{std::size_t(idx)...}[I]) *
meta::pack_element<I + 1, N...>::value),
0UL)...};
return index + unpack{std::size_t(idx)...}[sizeof...(idx) - 1];
}
// Storage
T m_data[meta::product<N...>::value];
//...
};
I have managed to get constexpr element access but only in C++14. The problem is the function linearized_index. It computes the linearized index at compile-time. In order to do so it reduces the tuple of indices and the tuple of dimension in a certain manner. For this reduction I need a local variable inside the function but this is not allowed in C++11. My environment does not permit the usage of C++14. Can I somehow rewrite this function to work with C++11?
I have prepared a full (not so minimal) example which compiles in C++14.
#include <cstddef> // std::size_t
namespace meta {
// product
template <std::size_t...>
struct product;
template <std::size_t head, std::size_t... dim>
struct product<head, dim...> {
static constexpr std::size_t const value = head * product<dim...>::value;
};
template <>
struct product<> {
static constexpr std::size_t const value = 1;
};
// pack_element
template <std::size_t index, std::size_t head, std::size_t... pack>
struct pack_element {
static_assert(index < sizeof...(pack) + 1, "index out of bounds");
static constexpr std::size_t const value =
pack_element<index - 1, pack...>::value;
};
template <std::size_t head, std::size_t... pack>
struct pack_element<0, head, pack...> {
static constexpr std::size_t const value = head;
};
// index_sequence
// https://stackoverflow.com/a/24481400
template <std::size_t... I>
struct index_sequence {};
template <std::size_t N, std::size_t... I>
struct make_index_sequence : public make_index_sequence<N - 1, N - 1, I...> {};
template <std::size_t... I>
struct make_index_sequence<0, I...> : public index_sequence<I...> {};
} // namespace meta
template <typename T, std::size_t... N>
class multi_array {
template <std::size_t... I, typename... Idx>
constexpr std::size_t linearized_index(meta::index_sequence<I...>,
Idx... idx) const {
std::size_t index = 0;
using unpack = std::size_t[];
(void)unpack{0UL,
((void)(index = (index + unpack{std::size_t(idx)...}[I]) *
meta::pack_element<I + 1, N...>::value),
0UL)...};
return index + unpack{std::size_t(idx)...}[sizeof...(idx) - 1];
}
// Storage
T m_data[meta::product<N...>::value];
public:
constexpr multi_array() {}
template <typename... U>
constexpr multi_array(U... data) : m_data{T(data)...} {}
template <typename... Idx>
constexpr T operator()(Idx... idx) const noexcept {
std::size_t index = linearized_index(
meta::make_index_sequence<sizeof...(idx) - 1>{}, idx...);
return m_data[index];
}
};
int main() {
constexpr multi_array<double, 2, 2> const b = {0, 0, 0, 1};
static_assert(b(1, 1) == 1, "!");
}
Live on Wandbox (C++14) and
Live on Wandbox (C++11)
The crucial part of your use of index is an iterative loop:
index = (index*a) + b
In your own C++14 solution, a trick of unpacking parameter pack is used. In C++11, you can formulate it in a recursive constexpr function:
struct mypair {
size_t a;
size_t b;
};
constexpr std::size_t foo(std::size_t init) {
return init;
}
template<class... Pair>
constexpr std::size_t foo(std::size_t init, mypair p0, Pair... ps) {
return foo((init+p0.a)*p0.b, ps...);
}
We use mypair instead of std::pair because the constructor of std::pair in C++11 is not constexpr. Then your iterative loop can be literally translated to:
template <std::size_t... I, typename... Idx>
constexpr std::size_t linearized_index(meta::index_sequence<I...>,
Idx... idx) const {
using unpack = std::size_t[];
return foo(0, mypair{unpack{std::size_t(idx)...}[I], meta::pack_element<I+1, N...>::value}...) + unpack{std::size_t(idx)...}[sizeof...(idx) - 1];
}
Live Demo
If in operator(), instead of calling
std::size_t index = linearized_index(
meta::make_index_sequence<sizeof...(idx) - 1>{}, idx...);
you call
std::size_t index = linearized_index<N...>(idx...);
or better (to make operator() constexpr)
return m_data[linearized_index<N...>(idx...)];
you can rewrite linearized_index() recursively as follows
// ground case
template <std::size_t>
constexpr std::size_t linearized_index (std::size_t idx0) const
{ return idx0; }
// recursive case
template <std::size_t, std::size_t... Is, typename... Idx>
constexpr std::size_t linearized_index (std::size_t idx0,
Idx ... idxs) const
{ return idx0 * meta::product<Is...>::value
+ linearized_index<Is...>(idxs...); }
If you prefer, the ground case can be written as follows
template <typename = void>
constexpr std::size_t linearized_index () const
{ return 0; }
Observe that you don't need meta::index_sequence, meta::make_index_sequence or meta::pack_element anymore.
The following is a full C++11 compiling example
#include <cstddef> // std::size_t
namespace meta
{
template <std::size_t...>
struct product;
template <std::size_t head, std::size_t... dim>
struct product<head, dim...>
{ static constexpr std::size_t const value
= head * product<dim...>::value; };
template <>
struct product<>
{ static constexpr std::size_t const value = 1; };
} // namespace meta
template <typename T, std::size_t... N>
class multi_array
{
private:
// ground case
template <std::size_t>
constexpr std::size_t linearized_index (std::size_t idx0) const
{ return idx0; }
// alternative ground case
//template <typename = void>
//constexpr std::size_t linearized_index () const
// { return 0; }
// recursive case
template <std::size_t, std::size_t... Is, typename... Idx>
constexpr std::size_t linearized_index (std::size_t idx0,
Idx ... idxs) const
{ return idx0 * meta::product<Is...>::value
+ linearized_index<Is...>(idxs...); }
// Storage
T m_data[meta::product<N...>::value];
public:
constexpr multi_array()
{ }
template <typename ... U>
constexpr multi_array(U ... data) : m_data{T(data)...}
{ }
template <typename... Idx>
constexpr T operator() (Idx... idx) const noexcept
{ return m_data[linearized_index<N...>(idx...)]; }
};
int main()
{
constexpr multi_array<double, 2, 2> const b = {0, 0, 0, 1};
static_assert( b(1, 1) == 1, "!" );
constexpr multi_array<double, 4, 3, 2> const c
{ 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 2, 0, 0, 0,
0, 0, 0, 0, 0, 1};
static_assert( c(3, 2, 1) == 1, "!" );
static_assert( c(2, 1, 0) == 2, "!" );
}
Bonus suggestion: if you add the following constexpr functions (static methods inside multi_array?)
constexpr static std::size_t prod ()
{ return 1U; }
template <typename ... Args>
constexpr static std::size_t prod (std::size_t v, Args ... vs)
{ return v * prod(vs...); }
you can delete the struct product calling
// Storage
T m_data[prod(N...)];
and
// recursive case
template <std::size_t, std::size_t... Is, typename... Idx>
constexpr std::size_t linearized_index (std::size_t idx0,
Idx ... idxs) const
{ return idx0 * prod(Is...) + linearized_index<Is...>(idxs...); }
Straightforward approach avoiding arrays:
#include <tuple>
#include <type_traits>
#include <cstddef>
template
<
typename x_IndexTypesTuple
, ::std::size_t ... x_dimension
> class
t_MultiIndexImpl;
template
<
typename x_LeadingIndex
, typename ... x_Index
, ::std::size_t x_leadding_dimension
, ::std::size_t ... x_dimension
> class
t_MultiIndexImpl
<
::std::tuple<x_LeadingIndex, x_Index ...>, x_leadding_dimension, x_dimension ...
> final
{
static_assert
(
::std::is_same<::std::size_t, x_LeadingIndex>::value
, "index type must be ::std::size_t"
);
public: static constexpr auto
Op
(
::std::size_t const stride_size
, x_LeadingIndex const leading_index
, x_Index const ... index
) -> ::std::size_t
{
return stride_size * leading_index
+ t_MultiIndexImpl<::std::tuple<x_Index ...>, x_dimension ...>::Op
(
stride_size * x_leadding_dimension, index ...
);
}
};
template<> class
t_MultiIndexImpl<::std::tuple<>> final
{
public: static constexpr auto
Op(::std::size_t const /*stride_size*/) -> ::std::size_t
{
return ::std::size_t{0};
}
};
template<::std::size_t ... x_dimension, typename ... x_Index> inline constexpr auto
Caclculate_MultiIndex(x_Index const ... index) -> ::std::size_t
{
static_assert
(
sizeof...(x_dimension) == sizeof...(x_Index)
, "arguments count must match dimensions count"
);
return t_MultiIndexImpl<::std::tuple<x_Index ...>, x_dimension ...>::Op(::std::size_t{1}, index ...);
}
online compiler |
godbolt
I have managed to get a C++11 compatible solution by rewriting the function to evaluate recursively. This not only works but is also a lot nicer to read:
template <typename... Idx>
constexpr std::size_t linearized_index(std::size_t n, Idx... idx) const {
using unpack = std::size_t[];
return unpack{std::size_t(idx)...}[n] +
(n == 0 ? 0
: unpack{std::size_t(N)...}[n] *
linearized_index(n - 1, idx...));
}
I have found another solution using template specializations which avoids the recursive function call and replaces it with recursive instantiation.
namespace meta {
template <size_t n, size_t... N>
struct linearized_index {
template <typename... Idx>
constexpr std::size_t operator()(Idx... idx) const {
using unpack = std::size_t[];
return unpack{std::size_t(idx)...}[n] +
unpack{std::size_t(N)...}[n] *
linearized_index<n - 1, N...>{}(idx...);
}
};
template <size_t... N>
struct linearized_index<0, N...> {
template <typename... Idx>
constexpr std::size_t operator()(Idx... idx) const {
using unpack = std::size_t[];
return unpack{std::size_t(idx)...}[0];
}
};
} // namespace meta
and the multi_array call operator
template <typename... Idx>
constexpr T operator()(Idx... idx) const noexcept {
return m_data[meta::linearized_index<sizeof...(idx) - 1, N...>{}(
idx...)];
}
This produces perfect assembly: https://godbolt.org/g/8LPkBZ

Building static strings with C++ Metaprogramming

I'm writing an open source sqlite interface library (mSqliteCpp for instance) that uses c++ classes and types to correctly manage sqlite databases.
So far the library is heavily using TMP to build SQL strings, but I found a possible issue that may somehow affect the efficiency of the library.
I'm using classes to manage Fields definitions, classes definitions, queries and statements. Basically to define a table or a SELECT statement, one defines the fields using proper FieldDef<T> objects, then pass them to a statement builder that returns the correctly formatted SQL statement.
For example:
auto fldId = sqlite::makeFieldDef("id", sqlite::FieldType::Integer());
auto fldName = sqlite::makeFieldDef("name", sqlite::FieldType::Text());
auto fldValue = sqlite::makeFieldDef("value", sqlite::FieldType::Real());
sqlite::statements::Select select("Table", fldId, fldName, fldValue);
ASSERT_EQ(select.string(), "SELECT id,name,value FROM Table;");
Note that each field is strongly typed by the FieldType type passed to the makeFieldDef function, but different fields with similar type can be exchanged. For this reason the SQL statement in the ASSERT call is built at runtime.
I would like to have them built at compile time, at least under certain conditions. For example, developer could use a different class or maybe the constexpr keyword. But at this time I've no idea if this could be achieved.
What are the possible patterns? Can strings be statically built through TMP? I'm using C++11/14.
Thank you.
what I'm looking for is a solution that will make most of the computation with strings at compile time [...] At the moment I'm just courious about how this could be implemented.
Not an answer to your sqlite question but... if your "computation with strings at compile time" are simple as concatenation... just to play with constexpr and template metaprogramming...
std::string isn't constexpr but std::array can be.
So you can define a fake string as an alias for an array of chars.
template <std::size_t N>
using fakeStr = std::array<char, N>;
You can convert a string literal to a fake string as follows (with an helper function and playing with type traits)
template <std::size_t N, std::size_t ... Is>
constexpr fakeStr<sizeof...(Is)> mFSh (char const (& str)[N],
std::index_sequence<Is...> const &)
{ return { { str[Is]... } }; }
template <std::size_t N>
constexpr auto makeFakeStr (char const (& str)[N])
{ return mFSh(str, std::make_index_sequence<N-1>{}); }
The fake string concatenation is simple as follows
template <std::size_t N1, std::size_t ... I1s,
std::size_t N2, std::size_t ... I2s>
constexpr fakeStr<N1+N2> cFSh (fakeStr<N1> const & s1,
std::index_sequence<I1s...> const &,
fakeStr<N2> const & s2,
std::index_sequence<I2s...> const &)
{ return { { s1[I1s]..., s2[I2s]... } }; }
template <std::size_t N1, std::size_t N2>
constexpr auto concatFakeStr (fakeStr<N1> const & s1, fakeStr<N2> const & s2)
{ return cFSh(s1, std::make_index_sequence<N1>{},
s2, std::make_index_sequence<N2>{}); }
and a constexpr comparison can be done as follows (with C++17 the helper function can be simpler)
template <std::size_t N1, std::size_t N2, std::size_t ... Is>
constexpr bool cmpFSh (fakeStr<N1> const & s1,
fakeStr<N2> const & s2,
std::index_sequence<Is...> const &)
{
using unused = bool[];
bool ret { true };
(void) unused { true, ret &= s1[Is] == s2[Is] ... };
return ret;
}
template <std::size_t N1, std::size_t N2>
constexpr bool compareFakeStr (fakeStr<N1> const & s1, fakeStr<N2> const & s2)
{ return (N1 == N2) && cmpFSh(s1, s2, std::make_index_sequence<N1>{}); }
If you want a std::string from a fakeStr, the following code (not constexpr, obviously) can help
template <std::size_t N, std::size_t ... Is>
std::string fFSh (fakeStr<N> const & s, std::index_sequence<Is...> const &)
{ return { s[Is]..., '\0' }; }
template <std::size_t N>
auto fromFakeString (fakeStr<N> const & s)
{ return fFSh(s, std::make_index_sequence<N>{}); }
The following is a full (C++14) working example
#include <array>
#include <string>
#include <iostream>
#include <type_traits>
template <std::size_t N>
using fakeStr = std::array<char, N>;
template <std::size_t N, std::size_t ... Is>
constexpr fakeStr<sizeof...(Is)> mFSh (char const (& str)[N],
std::index_sequence<Is...> const &)
{ return { { str[Is]... } }; }
template <std::size_t N>
constexpr auto makeFakeStr (char const (& str)[N])
{ return mFSh(str, std::make_index_sequence<N-1>{}); }
template <std::size_t N1, std::size_t ... I1s,
std::size_t N2, std::size_t ... I2s>
constexpr fakeStr<N1+N2> cFSh (fakeStr<N1> const & s1,
std::index_sequence<I1s...> const &,
fakeStr<N2> const & s2,
std::index_sequence<I2s...> const &)
{ return { { s1[I1s]..., s2[I2s]... } }; }
template <std::size_t N1, std::size_t N2>
constexpr auto concatFakeStr (fakeStr<N1> const & s1, fakeStr<N2> const & s2)
{ return cFSh(s1, std::make_index_sequence<N1>{},
s2, std::make_index_sequence<N2>{}); }
template <std::size_t N1, std::size_t N2, std::size_t ... Is>
constexpr bool cmpFSh (fakeStr<N1> const & s1,
fakeStr<N2> const & s2,
std::index_sequence<Is...> const &)
{
using unused = bool[];
bool ret { true };
(void) unused { true, ret &= s1[Is] == s2[Is] ... };
return ret;
}
template <std::size_t N1, std::size_t N2>
constexpr bool compareFakeStr (fakeStr<N1> const & s1, fakeStr<N2> const & s2)
{ return (N1 == N2) && cmpFSh(s1, s2, std::make_index_sequence<N1>{}); }
template <std::size_t N, std::size_t ... Is>
std::string fFSh (fakeStr<N> const & s, std::index_sequence<Is...> const &)
{ return { s[Is]..., '\0' }; }
template <std::size_t N>
auto fromFakeString (fakeStr<N> const & s)
{ return fFSh(s, std::make_index_sequence<N>{}); }
int main()
{
constexpr auto f1 = makeFakeStr("abcd");
constexpr auto f2 = makeFakeStr("xyz");
constexpr auto f12 = concatFakeStr(f1, f2);
constexpr auto f3 = makeFakeStr("abcdxyz");
static_assert( true == compareFakeStr(f12, f3), "!" );
static_assert( false == compareFakeStr(f12, f1), "!" );
auto s12 = fromFakeString(f12);
std::cout << s12 << std::endl;
}
I repeat: just to play.
#max66's solution is probably better, but there is a proof of concept of a different approach: to store strings as char parameter packs:
template <char ...C> struct cexpr_str
{
static constexpr char value[] {C..., '\0'};
};
Here is a complete example, demonstrating how to create such strings from string literals, concatenate, and compare them:
#include <cstddef>
#include <iostream>
#include <type_traits>
#include <utility>
template <char ...C> struct cexpr_str
{
static constexpr char value[] {C..., '\0'};
};
namespace impl
{
using std::size_t;
template <typename ...P> struct cexpr_cat
{
using type = cexpr_str<>;
};
template <typename A, typename B, typename ...P> struct cexpr_cat<A, B, P...>
{
using type = typename cexpr_cat<typename cexpr_cat<A, B>::type, P...>::type;
};
template <char ...A, char ...B> struct cexpr_cat<cexpr_str<A...>, cexpr_str<B...>>
{
using type = cexpr_str<A..., B...>;
};
template <typename T> struct cexpr_cat<T>
{
using type = T;
};
template <typename, typename> struct cexpr_str_subseq {};
template <size_t ...S, char ...C> struct cexpr_str_subseq<std::index_sequence<S...>, cexpr_str<C...>>
{
using type = cexpr_str<cexpr_str<C...>::value[S]...>;
};
template <size_t N> constexpr char cexpr_str_at(const char (&str)[N], size_t pos)
{
if (pos < 0 || pos >= N)
return '\0';
else
return str[pos];
}
}
template <typename ...P> using cexpr_cat = typename impl::cexpr_cat<P...>::type;
#define MAKE_CEXPR_STR(x) ::impl::cexpr_str_subseq<::std::make_index_sequence<sizeof x - 1>,\
::cexpr_str<CEXPR_STR_16(0,x),CEXPR_STR_16(16,x),CEXPR_STR_16(32,x),CEXPR_STR_16(48,x)>>::type
#define CEXPR_STR_16(s,x) CEXPR_STR_4(s,x),CEXPR_STR_4(s+4,x),CEXPR_STR_4(s+8,x),CEXPR_STR_4(s+12,x)
#define CEXPR_STR_4(s,x) ::impl::cexpr_str_at(x,s),::impl::cexpr_str_at(x,s+1),::impl::cexpr_str_at(x,s+2),::impl::cexpr_str_at(x,s+3)
int main()
{
// You can use this macro to create a string.
// Note that it limits the length to 64, but can be easily rewritten to allow larger values.
using A = MAKE_CEXPR_STR("Hello,");
// If you don't want to use that macro, you can do this.
using B = cexpr_str<'w','o','r','l','d'>;
// You can concat any number of comma-separated strings, not just two.
using C = cexpr_cat<A,B>;
// This prints: `Hello,`+`world`=`Hello,world`.
std::cout << "`" << A::value << "`+`" << B::value << "`=`" << C::value << "`\n";
// You can use std::is_same[_v] to compare those strings:
std::cout << std::is_same_v<B,A> << '\n'; // Prints 0.
std::cout << std::is_same_v<B,MAKE_CEXPR_STR("world")> << '\n'; // Prints 1.
}

Implementing mathematical complement logic on variadic template parameters

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)>;

Concatenating a sequence of std::arrays

Consider the following: (Wandbox)
#include <array>
#include <algorithm>
#include <iostream>
template<typename T, int N, int M>
auto concat(const std::array<T, N>& ar1, const std::array<T, M>& ar2)
{
std::array<T, N+M> result;
std::copy (ar1.cbegin(), ar1.cend(), result.begin());
std::copy (ar2.cbegin(), ar2.cend(), result.begin() + N);
return result;
}
int main()
{
std::array<int, 3> ar1 = {1, 2, 3};
std::array<int, 2> ar2 = {4, 5};
auto result = concat<int, 3, 2>(ar1, ar2);
for (auto& x : result)
std::cout << x << " ";
std::cout << std::endl;
return 0;
}
Given a sequence of std::array<T, length1>, std::array<T, length2>, ..., std::array<T, lengthK>, how can I generalize the above code and write a function which concatenates the sequence into an std::array<T, sum(lengths)>?
It would be nice if there is a way to write a reusable function which reduces a similar sequence of template classes using a given binary operation, e.g., use concat in the example above, rather than writing a special method (which would have to be re-written each time the binary op changes).
(IIUC, the relevant Standard Library algorithms (accumulate, reduce) only work in case the class of the result of the binary operation is always the same.)
Here is a simple C++17 solution via fold expressions:
#include <array>
#include <algorithm>
template <typename Type, std::size_t... sizes>
auto concatenate(const std::array<Type, sizes>&... arrays)
{
std::array<Type, (sizes + ...)> result;
std::size_t index{};
((std::copy_n(arrays.begin(), sizes, result.begin() + index), index += sizes), ...);
return result;
}
Example of using:
const std::array<int, 3> array1 = {{1, 2, 3}};
const std::array<int, 2> array2 = {{4, 5}};
const std::array<int, 4> array3 = {{6, 7, 8, 9}};
const auto result = concatenate(array1, array2, array3);
Live demo
You may do the following:
template <typename F, typename T, typename T2>
auto func(F f, T&& t, T2&& t2)
{
return f(std::forward<T>(t), std::forward<T2>(t2));
}
template <typename F, typename T, typename T2, typename ... Ts>
auto func(F f, T&& t, T2&& t2, Ts&&...args)
{
return func(f, f(std::forward<T>(t), std::forward<T2>(t2)), std::forward<Ts>(args)...);
}
With usage
struct concatenater
{
template<typename T, std::size_t N, std::size_t M>
auto operator()(const std::array<T, N>& ar1, const std::array<T, M>& ar2) const
{
std::array<T, N+M> result;
std::copy (ar1.cbegin(), ar1.cend(), result.begin());
std::copy (ar2.cbegin(), ar2.cend(), result.begin() + N);
return result;
}
};
and
auto result = func(concatenater{}, ar1, ar2, ar3, ar4);
C++14 Demo
C++11 Demo
Given a sequence of std::array<T, length1>, std::array<T, length2>, ..., std::array<T, lengthK>, how can I write a function which concatenates the sequence into an std::array<T, sum(lengths)>?
Here's a C++17 solution. It can very probably be shortened and improved, working on it.
template <std::size_t Last = 0, typename TF, typename TArray, typename... TRest>
constexpr auto with_acc_sizes(TF&& f, const TArray& array, const TRest&... rest)
{
f(array, std::integral_constant<std::size_t, Last>{});
if constexpr(sizeof...(TRest) != 0)
{
with_acc_sizes<Last + std::tuple_size_v<TArray>>(f, rest...);
}
}
template<typename T, std::size_t... Sizes>
constexpr auto concat(const std::array<T, Sizes>&... arrays)
{
std::array<T, (Sizes + ...)> result{};
with_acc_sizes([&](const auto& arr, auto offset)
{
std::copy(arr.begin(), arr.end(), result.begin() + offset);
}, arrays...);
return result;
}
Usage:
std::array<int, 3> ar1 = {1, 2, 3};
std::array<int, 2> ar2 = {4, 5};
std::array<int, 3> ar3 = {6, 7, 8};
auto result = concat(ar1, ar2, ar3);
live wandbox example
Works with both g++7 and clang++5.
My first line of reasoning would be to consider converting the array to a tuple of references (a tie), manipulate with tuple_cat and then perform whatever operation is necessary to build the final array (i.e. either move or copy - depending on the arguments originally passed in):
#include <array>
#include <iostream>
namespace detail {
template<class Array, std::size_t...Is>
auto array_as_tie(Array &a, std::index_sequence<Is...>) {
return std::tie(a[Is]...);
};
template<class T, class Tuple, std::size_t...Is>
auto copy_to_array(Tuple &t, std::index_sequence<Is...>) {
return std::array<T, sizeof...(Is)>
{
std::get<Is>(t)...
};
};
template<class T, class Tuple, std::size_t...Is>
auto move_to_array(Tuple &t, std::index_sequence<Is...>) {
return std::array<T, sizeof...(Is)>
{
std::move(std::get<Is>(t))...
};
};
}
template<class T, std::size_t N>
auto array_as_tie(std::array<T, N> &a) {
return detail::array_as_tie(a, std::make_index_sequence<N>());
};
// various overloads for different combinations of lvalues and rvalues - needs some work
// for instance, does not handle mixed lvalues and rvalues yet
template<class T, std::size_t N1, std::size_t N2>
auto array_cat(std::array<T, N1> &a1, std::array<T, N2> &a2) {
auto tied = std::tuple_cat(array_as_tie(a1), array_as_tie(a2));
return detail::copy_to_array<T>(tied, std::make_index_sequence<N1 + N2>());
};
template<class T, std::size_t N1, std::size_t N2>
auto array_cat(std::array<T, N1> &&a1, std::array<T, N2> &&a2) {
auto tied = std::tuple_cat(array_as_tie(a1), array_as_tie(a2));
return detail::move_to_array<T>(tied, std::make_index_sequence<N1 + N2>());
};
int main() {
std::array<int, 3> ar1 = {1, 2, 3};
std::array<int, 2> ar2 = {4, 5};
auto result = array_cat(ar1, ar2);
for (auto &x : result)
std::cout << x << " ";
std::cout << std::endl;
// move-construction
auto r2 = array_cat(std::array<std::string, 2> {"a", "b"},
std::array<std::string, 2>{"asdfghjk", "qwertyui"});
std::cout << "string result:\n";
for (auto &&x : r2)
std::cout << x << " ";
std::cout << std::endl;
return 0;
}
expected results:
1 2 3 4 5
string result:
a b asdfghjk qwertyui
A more concise evolution of #Constructor's C++17 solution with the added benefit that Type is not required to be default constructible
template <typename Type, std::size_t... sizes>
constexpr auto concatenate(const std::array<Type, sizes>&... arrays)
{
return std::apply(
[] (auto... elems) -> std::array<Type, (sizes + ...)> { return {{ elems... }}; },
std::tuple_cat(std::tuple_cat(arrays)...));
}
C++14.
template<std::size_t I>
using index_t=std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index{};
template<std::size_t...Is>
auto index_over(std::index_sequence<Is...>){
return [](auto&&f)->decltype(auto){
return decltype(f)(f)( index<Is>... );
};
}
template<std::size_t N>
auto index_upto(index_t<N>={}){
return index_over(std::make_index_sequence<N>{});
}
this lets us expand parameter packs inline.
template<std::size_t, class T>
using indexed_type=T;
template<class T>
std::decay_t<T> concat_arrays( T&& in ){ return std::forward<T>(in); }
template<class T, std::size_t N0, std::size_t N1 >
std::array<T, N0+N1>
concat_arrays( std::array<T,N0> arr0, std::array<T,N1> arr1 ){
auto idx0 = index_upto<N0>();
auto idx1 = index_upto<N1>();
return idx0( [&](auto...I0s){
return idx1( [&](auto...I1s)->std::array<T, N0+N1>{
return {{
arr0[I0s]...,
arr1[I1s]...
}};
})
});
}
which gets us to two. For N, the easy way is:
template<class T, std::size_t N0, std::size_t N1, std::size_t...Ns >
auto concat_arrays( std::array<T,N0> arr0, std::array<T,N1> arr1, std::array<T, Ns>... arrs ){
return concat_arrays( std::move(arr0), concat_arrays( std::move(arr1), std::move(arrs)... ) );
}
but it should be possible without recursion.
Code not tested.
C++17 solution that is constexpr and works correctly with moveably-only types.
template<class Array>
inline constexpr auto array_size = std::tuple_size_v<std::remove_reference_t<Array>>;
template<typename... Ts>
constexpr auto make_array(Ts&&... values)
{
using T = std::common_type_t<Ts...>;
return std::array<T, sizeof...(Ts)>{static_cast<T>(std::forward<Ts>(values))...};
}
namespace detail
{
template<typename Arr1, typename Arr2, std::size_t... is1, std::size_t... is2>
constexpr auto array_cat(Arr1&& arr1, Arr2&& arr2, std::index_sequence<is1...>, std::index_sequence<is2...>)
{
return make_array(std::get<is1>(std::forward<Arr1>(arr1))...,
std::get<is2>(std::forward<Arr2>(arr2))...);
}
}
template<typename Arr, typename... Arrs>
constexpr auto array_cat(Arr&& arr, Arrs&&... arrs)
{
if constexpr (sizeof...(Arrs) == 0)
return std::forward<Arr>(arr);
else if constexpr (sizeof...(Arrs) == 1)
return detail::array_cat(std::forward<Arr>(arr), std::forward<Arrs>(arrs)...,
std::make_index_sequence<array_size<Arr>>{},
std::make_index_sequence<array_size<Arrs...>>{});
else
return array_cat(std::forward<Arr>(arr), array_cat(std::forward<Arrs>(arrs)...));
}
Strictly C++11; not as readable as #Jarod42's, but potentially much more efficient with many arrays if the call-tree isn't fully flattened (in terms of inlining) since only one result object exists rather than multiple temporary, progressively-growing result objects:
namespace detail {
template<std::size_t...>
struct sum_sizes_;
template<std::size_t Acc>
struct sum_sizes_<Acc> : std::integral_constant<std::size_t, Acc> { };
template<std::size_t Acc, std::size_t N, std::size_t... Ns>
struct sum_sizes_<Acc, N, Ns...> : sum_sizes_<Acc + N, Ns...> { };
template<typename... As>
using sum_sizes_t = typename sum_sizes_<
0, std::tuple_size<typename std::decay<As>::type>{}...
>::type;
template<std::size_t O, typename A, typename R>
void transfer(R& ret, typename std::remove_reference<A>::type const& a) {
std::copy(a.begin(), a.end(), ret.begin() + O);
}
template<std::size_t O, typename A, typename R>
void transfer(R& ret, typename std::remove_reference<A>::type&& a) {
std::move(a.begin(), a.end(), ret.begin() + O);
}
template<std::size_t, typename R>
void concat(R const&) { }
template<std::size_t O, typename R, typename A, typename... As>
void concat(R& ret, A&& a, As&&... as) {
transfer<O, A>(ret, std::forward<A>(a));
concat<(O + sum_sizes_t<A>{})>(ret, std::forward<As>(as)...);
}
}
template<typename... As, typename std::enable_if<(sizeof...(As) >= 2), int>::type = 0>
auto concat(As&&... as)
-> std::array<
typename std::common_type<typename std::decay<As>::type::value_type...>::type,
detail::sum_sizes_t<As...>{}
> {
decltype(concat(std::forward<As>(as)...)) ret;
detail::concat<0>(ret, std::forward<As>(as)...);
return ret;
}
Online Demo
Note that this also forwards properly by using the std::move algorithm for rvalues rather than std::copy.
This doesn't generalize, but takes advantage of the fact that if we splat two arrays inside a set of braces, we can use that to initialize a new array.
I'm not sure how useful generalizing is, in any case. Given a bunch of arrays of mismatched sizes, just what else is there to do with them but join them all together?
#include <array>
#include <iostream>
#include <utility>
template<typename T, std::size_t L, std::size_t... Ls,
std::size_t R, std::size_t... Rs>
constexpr std::array<T, L + R> concat_aux(const std::array<T, L>& l, std::index_sequence<Ls...>,
const std::array<T, R>& r, std::index_sequence<Rs...>) {
return std::array<T, L + R> { std::get<Ls>(l)..., std::get<Rs>(r)... };
}
template<typename T, std::size_t L, std::size_t R>
constexpr std::array<T, L + R> concat(const std::array<T, L>& l, const std::array<T, R>& r) {
return concat_aux(l, std::make_index_sequence<L>{},
r, std::make_index_sequence<R>{});
}
template<typename T, std::size_t L, std::size_t R, std::size_t... Sizes>
constexpr auto concat(const std::array<T, L>& l,
const std::array<T, R>& r,
const std::array<T, Sizes>&... arrays) {
return concat(concat(l, r), arrays...);
}
int main() {
std::array<int, 5> a1{1, 2, 3, 4, 5};
std::array<int, 3> a2{6, 7, 8};
std::array<int, 2> a3{9, 10};
for (const auto& elem : concat(a1, a2, a3)) {
std::cout << elem << " ";
}
}