I took a look at your question, and noticed that no one really answered it. My diversion to answer it took all day to perfect, but I needed it anyway for my work.
My work needs constexpr
, and I wrote the answer with that in mind. Your work doesn't specify that, but it can't hurt.
What didn't work; i.e. my first try
(I'm using GCC-4.7, from MacPorts, on a 10-year old PowerPC Mac.)
You can easily turn a (C++11) variadic function parameter list to any kind of tuple:
template < typename Destination, typename ...Source >
constexpr
auto initialize( Source&& ...args ) -> Destination
{ return Destination{ce_forward<Source>(args)...}; }
(The ce_forward
function template is just like std::forward
, except I explicitly made it constexpr
.)
(When I didn't put Destination
in the body, my compiler gave me errors relating to the destination not being able to be initialized with a std::initialization_list
; so the form I have now should work with any aggregate or constructor form the destination type supports.)
But we need to initially go the other way, then use my code above to translate back. I tried code like this:
template < typename Destination, typename Source, typename Size1, typename Size2, typename ...Args >
constexpr
auto fill_from_array( Source&& source, Size1 index_begin, Size2 index_end, Args&& ...args )
-> Destination
{
return ( index_begin < index_end )
? fill_from_array<Destination>( ce_forward<Source>(source), index_begin + 1, index_end, ce_forward<Args>(args)..., ce_forward<Source>(source)[index_begin] )
: initialize<Destination>( ce_forward<Args>(args)... );
}
(Since I needed two sources too, I made a bigger version of this function.)
When I actually ran this code, my computer crapped out after an hour from exceeding virtual memory. I guess this causes an infinite loop or something. (Or maybe it's finite, but too much for my ancient system.)
My second try
I scoured S.O. until I found stuff that could be useful:
and pieced a solution from those and something else I read: Parsing strings at compile-time — Part I. Basically, we use a variant of my initialize
function template above; instead of basing the initializers purely off function variadic parameters, we use a mapping from template variadic parameters.
The easiest mapping source is the nonnegative integers:
#include <cstddef>
template < std::size_t ...Indices >
struct index_tuple
{ using next = index_tuple<Indices..., sizeof...(Indices)>; };
template < std::size_t Size >
struct build_indices
{ using type = typename build_indices<Size - 1>::type::next; };
template < >
struct build_indices< 0 >
{ using type = index_tuple<>; };
The index_tuple
class template is what we'll be passing around for mapping, while the build_indices
class template puts the index_tuple
instantiations in the right format:
index_tuple<>
index_tuple<0>
index_tuple<0, 1>
index_tuple<0, 1, 2>
...
We create index_tuple
objects with a function template:
template < std::size_t Size >
constexpr
auto make_indices() noexcept -> typename build_indices<Size>::type
{ return {}; }
We use said index_tuple
objects for their contribution to a function template's template header:
#include <array>
template < std::size_t N, std::size_t M, std::size_t ...Indices >
constexpr
std::array<char, N + M - 1u>
fuse_strings_impl( const char (&f)[N], const char (&s)[M], index_tuple<Indices...> );
The third parameter doesn't get a name because we won't need the object itself. We just need the "std::size_t ...Indices" in the header. We know that will turn into a "0, 1, ..., X" when expanded. We'll feed that orderly expansion into a function call that gets expanded into the required initializers. As an example, let's look at the definition of the function above:
template < std::size_t N, std::size_t M, std::size_t ...Indices >
constexpr
std::array<char, N + M - 1u>
fuse_strings_impl( const char (&f)[N], const char (&s)[M], index_tuple<Indices...> )
{ return {{ get_strchr<Indices>(f, s)... }}; }
We'll be returning an array
with the first element as get_strchr<0>(f,s)
, the second as get_strchr<1>(f,s)
, and so on. Note that this function name ends with "_impl" because I hide the use of index_tuple
and ensure a proper base case by calling the public version:
template < std::size_t N, std::size_t M >
constexpr
std::array<char, N + M - 1u>
fuse_strings( const char (&f)[N], const char (&s)[M] )
{ return fuse_strings_impl(f, s, make_indices<N + M - 2>()); }
And you can try to code as so:
#include <iostream>
#include <ostream>
int main()
{
using std::cout;
using std::endl;
constexpr auto initialize_test = initialize<std::array<char, 15>>( 'G',
'o', 'o', 'd', 'b', 'y', 'e', ',', ' ', 'm', 'o', 'o', 'n', '!', '\0' );
constexpr char hello_str[] = "Hello ";
constexpr char world_str[] = "world!";
constexpr auto hw = fuse_strings( hello_str, world_str );
cout << initialize_test.data() << endl;
cout << hw.data() << endl;
}
There are some subtleties to watch for.
- Your declarations of
const(expr) char str[] = "Whatever";
have to use []
instead of *
so the compiler recognizes your object as a built-in array, and not as a pointer to unknown (fixed) memory of run-time length.
- Since built-in arrays can't be used as return types, you have to use
std::array
as a substitute. The problem is when you have to use the result for a later merging. A std::array
object has to have a publicly available non-static data member of the appropriate built-in array type, but the name of that member is not specified in the standard and probably isn't consistent. So you have to create a std::array
work-alike to officially avoid hacks.
- You can't use the same code for general array joins and string joins. A string is a
char
array where the last element must be '\0'
. Those NUL
values must be skipped when reading from strings but added when writing one. That why the odd 1's and 2's appear in my code. For general array joins (including non-string char
ones), every element in every array must be read, and no extra elements should be added to the combined array.
Oh, here's the definition of get_strchr
:
template < std::size_t N >
constexpr
char ce_strchr( std::size_t i, const char (&s)[N] )
{
static_assert( N, "empty string" );
return (i < ( N - 1 )) ? s[i] : throw "too big";
}
template < std::size_t N, std::size_t M, std::size_t ...L >
constexpr
char ce_strchr( std::size_t i, const char (&f)[N], const char (&s)[M], const char (&...t)[L] )
{
static_assert( N, "empty string" );
return (i < ( N - 1 )) ? f[i] : ce_strchr(i + 1 - N, s, t...);
}
template < std::size_t I, std::size_t N, std::size_t ...M >
constexpr
char get_strchr( const char (&f)[N], const char (&...s)[M] )
{ return ce_strchr(I, f, s...); }
(I hope you get to read this.)