I'm trying to implement some kind of map (a tuple of pair) which use compile time string as key (first element of the pair). So I wanted to use this answer but there is a problem with my code : the string is inside a pair.
#include <type_traits>
#include <tuple>
namespace meta {
template < typename T >
struct CType { using type = T; };
namespace detail {
template <typename T>
struct typeid_t {
using type = typename std::remove_cv<
typename std::remove_reference<T>::type
>::type;
};
}
template <typename T>
constexpr decltype(auto) typeid_(T&&) {
return CType<typename detail::typeid_t<T>::type>{};
}
}
struct HashConstString {
using value_type = uint32_t;
static constexpr uint32_t hash(const char* str) {
return str[0];
}
};
template < typename T_Hash,
typename... T_Pairs >
class UniversalMap {
template < typename T_Pair >
using U_Pair = decltype(std::make_pair(
std::integral_constant<typename T_Hash::value_type, T_Hash::hash(std::get<0>(T_Pair{}))>{},
typename decltype(meta::typeid_(std::get<1>(T_Pair{})))::type {}
));
using U_Map = decltype(std::make_tuple(
U_Pair<T_Pairs>{}...
));
private:
U_Map m_map;
};
template < typename T_Hash,
typename... T_Pairs >
constexpr decltype(auto) make_UniversalMap(T_Hash hash, T_Pairs... pairs) {
(void)hash;
((void)pairs,...);
return UniversalMap<T_Hash, T_Pairs...>();
}
int main() {
constexpr auto hashValue = HashConstString::hash("Test");
constexpr auto map = make_UniversalMap(HashConstString{},
std::make_pair("Test", meta::CType<int>{})
);
}
Wandbox
So I don't know how to hash correctly the string when it's already inside the pair. Because std::get give me back a reference and it seems it's the reason why I have a dereferenced null pointer error.
Is there some "tricks" to get this work without having to compute the hash before creating the pair?
The problem is not with std::get but with the fact that you create a tuple of const char*. "Test" decays to const char* when passed as argument to make_pair. Unfortunately explicitly specifying the pair template parameters (e.g. std::pair<const char[5], int>) does not work because you can't create a std container of type array.
The rather awkward solution is to use std::array:
struct HashConstString
{
using value_type = uint32_t;
static constexpr uint32_t hash(const char *str) { return str[0]; }
// add this overload
template <std::size_t N>
static constexpr uint32_t hash(std::array<char, N> str) { return str[0]; }
};
and then call like this:
constexpr auto map = make_UniversalMap(HashConstString{},
std::make_pair(std::array<char, 5>{"Test"}, int{}));
To avoid specifying the size for std::array you can create a helper function:
template <std::size_t N> constexpr auto make_strarray(const char(&str)[N])
{
// unfortunately std::array<char, N>{str} does not work :(
std::array<char, N> arr{};
for (std::size_t i = 0; i < N; ++i)
arr[i] = str[i];
return arr;
}
Or since in C++20 it looks like std::copy will be made constexpr:
template <std::size_t N> constexpr auto make_strarray(const char(&str)[N])
{
std::array<char, N> arr{};
std::copy(str, str + N, arr.begin());
return arr;
}
Related
I am trying to write a function in order to generate arbitrarily nested vectors and initialize with the given specific value in C++. For example, auto test_vector = n_dim_vector_generator<2, long double>(static_cast<long double>(1), 1); is expected to create a "test_vector" object which type is std::vector<std::vector<long double>>. The content of this test_vector should as same as the following code.
std::vector<long double> vector1;
vector1.push_back(1);
std::vector<std::vector<long double>> test_vector;
test_vector.push_back(vector1);
The more complex usage of the n_dim_vector_generator function:
auto test_vector2 = n_dim_vector_generator<15, long double>(static_cast<long double>(2), 3);
In this case, the parameter static_cast<long double>(2) is as the data in vectors and the number 3 is as the push times. So, the content of this test_vector2 should as same as the following code.
std::vector<long double> vector1;
vector1.push_back(static_cast<long double>(2));
vector1.push_back(static_cast<long double>(2));
vector1.push_back(static_cast<long double>(2));
std::vector<std::vector<long double>> vector2;
vector2.push_back(vector1);
vector2.push_back(vector1);
vector2.push_back(vector1);
std::vector<std::vector<std::vector<long double>>> vector3;
vector3.push_back(vector2);
vector3.push_back(vector2);
vector3.push_back(vector2);
//...Totally repeat 15 times in order to create test_vector2
std::vector<...std::vector<long double>> test_vector2;
test_vector2.push_back(vector14);
test_vector2.push_back(vector14);
test_vector2.push_back(vector14);
The detail to implement n_dim_vector_generator function is as follows.
#include <iostream>
#include <vector>
template <typename T, std::size_t N>
struct n_dim_vector_type;
template <typename T>
struct n_dim_vector_type<T, 0> {
using type = T;
};
template <typename T, std::size_t N>
struct n_dim_vector_type {
using type = std::vector<typename n_dim_vector_type<T, N - 1>::type>;
};
template<std::size_t N, typename T>
typename n_dim_vector_type<T,N>::type n_dim_vector_generator(T t, unsigned int);
template <std::size_t N, typename T>
typename n_dim_vector_type<T, N>::type n_dim_vector_generator<N, T>(T input_data, unsigned int push_back_times) {
if (N == 0)
{
return std::move(input_data);
}
typename n_dim_vector_type<T, N>::type return_data;
for (size_t loop_number = 0; loop_number < push_back_times; loop_number++)
{
return_data.push_back(n_dim_vector_generator<N - 1, T>(input_data, push_back_times));
}
return return_data;
}
As a result, I got an error 'return': cannot convert from 'long double' to 'std::vector<std::vector<long double,std::allocator<long double>>,std::allocator<std::vector<long double,std::allocator<long double>>>>' I know that it caused by if (N == 0) block which is as the terminate condition to recursive structure. However, if I tried to edit the terminate condition into separate form.
template <typename T>
inline T n_dim_vector_generator<0, T>(T input_data, unsigned int push_back_times) {
return std::move(input_data);
}
template <std::size_t N, typename T>
typename n_dim_vector_type<T, N>::type n_dim_vector_generator<N, T>(T input_data, unsigned int push_back_times) {
typename n_dim_vector_type<T, N>::type return_data;
for (size_t loop_number = 0; loop_number < push_back_times; loop_number++)
{
return_data.push_back(n_dim_vector_generator<N - 1, T>(input_data, push_back_times));
}
return return_data;
}
The error 'n_dim_vector_generator': illegal use of explicit template arguments happened. Is there any better solution to this problem?
The develop environment is in Windows 10 1909 with Microsoft Visual Studio Enterprise 2019 Version 16.4.3
To achieve your specific mapping of:
auto test_vector = n_dim_vector_generator<2, long double>(2, 3)
to a 3x3 vector filled with 2's, your template can be a bit simpler if you take advantage of this vector constructor:
std::vector<std::vector<T>>(COUNT, std::vector<T>(...))
Since vector is copyable, this will fill COUNT slots with a different copy of the vector. So...
template <size_t N, typename T>
struct n_dim_vector_generator {
using type = std::vector<typename n_dim_vector_generator<N-1, T>::type>;
type operator()(T value, size_t size) {
return type(size, n_dim_vector_generator<N-1, T>{}(value, size));
}
};
template <typename T>
struct n_dim_vector_generator<0, T> {
using type = T;
type operator()(T value, size_t size) {
return value;
}
};
usage:
auto test_vector = n_dim_vector_generator<2, long double>{}(2, 3);
Demo: https://godbolt.org/z/eiDAUG
For the record, to address some concerns from the comments, C++ has an idiomatic, initializable, contiguous-memory class equivalent of a multi-dimension C array: a nested std::array:
std::array<std::array<long double, COLUMNS>, ROWS> test_array = { /*...*/ };
for (auto& row : test_array)
for (auto cell : row)
std::cout << cell << std::endl;
If you wanted to reduce the boilerplate to declare one, you can use a struct for that:
template <typename T, size_t... N>
struct multi_array;
template <typename T, size_t NFirst, size_t... N>
struct multi_array<T, NFirst, N...> {
using type = std::array<typename multi_array<T, N...>::type, NFirst>;
};
template <typename T, size_t NLast>
struct multi_array<T, NLast> {
using type = std::array<T, NLast>;
};
template <typename T, size_t... N>
using multi_array_t = typename multi_array<T, N...>::type;
Then to use:
multi_array_t<long double, ROWS, COLUMNS> test_array = { /*...*/ };
for (auto& row : test_array)
for (auto cell : row)
std::cout << cell << std::endl;
This is allocated on the stack, like a C array. That will eat up your stack space for a big array of course. But you can make a decorator range around std::unique_ptr to make a pointer to one a bit easier to access:
template <typename T, size_t... N>
struct dynamic_multi_array : std::unique_ptr<multi_array_t<T, N...>> {
using std::unique_ptr<multi_array_t<T, N...>>::unique_ptr;
constexpr typename multi_array_t<T, N...>::value_type& operator [](size_t index) { return (**this)[index]; }
constexpr const typename multi_array_t<T, N...>::value_type& operator [](size_t index) const { return (**this)[index]; }
constexpr typename multi_array_t<T, N...>::iterator begin() { return (**this).begin(); }
constexpr typename multi_array_t<T, N...>::iterator end() { return (**this).end(); }
constexpr typename multi_array_t<T, N...>::const_iterator begin() const { return (**this).begin(); }
constexpr typename multi_array_t<T, N...>::const_iterator end() const { return (**this).end(); }
constexpr typename multi_array_t<T, N...>::const_iterator cbegin() const { return (**this).cbegin(); }
constexpr typename multi_array_t<T, N...>::const_iterator cend() const { return (**this).cend(); }
constexpr typename multi_array_t<T, N...>::size_type size() const { return (**this).size(); }
constexpr bool empty() const { return (**this).empty(); }
constexpr typename multi_array_t<T, N...>::value_type* data() { return (**this).data(); }
constexpr const typename multi_array_t<T, N...>::value_type* data() const { return (**this).data(); }
};
(let the buyer beware if you use those methods with nullptr)
Then you can still brace-initialize a new expression and use it like a container:
dynamic_multi_array<long double, ROWS, COLUMNS> test_array {
new multi_array_t<long double, ROWS, COLUMNS> { /* ... */ }
};
for (auto& row : test_array)
for (auto cell : row)
std::cout << cell << std::endl;
Demo: https://godbolt.org/z/lUwVE_
I would like to initialize constexpr char[] member with another constexpr char [] member. Is it possible to do in C++11 or above?
#include <iostream>
struct Base {
static constexpr char ValueOne[] = "One";
static constexpr char ValueTwo[] = "Two";
};
template <typename T>
struct ValueOneHolder {
static constexpr char Value[] = T::ValueOne; // << How can one initialize this?
};
int main() {
std::cout << ValueOneHolder<Base>::Value << std::endl;
return 0;
}
I would like to initialize constexpr char[] member with another constexpr char [] member. Is it possible to do in C++11 or above?
Starting from C++14 you can use std::make_index_sequence and std::index_sequence.
If it's OK for you works in a ValueOneHolder specialization, you first can develop a constexpr function that, given a C-style array, return the size of the array
template <typename T, std::size_t N>
constexpr std::size_t getDim (T const (&)[N])
{ return N; }
Nest you can declare ValueOneHolder adding a second template parameter with a default value that is an index sequence corresponding to T::ValueOne
template <typename T,
typename = std::make_index_sequence<getDim(T::ValueOne)>>
struct ValueOneHolder;
and last the easy part: the partial specialization with initialization
template <typename T, std::size_t ... Is>
struct ValueOneHolder<T, std::index_sequence<Is...>>
{ static constexpr char Value[] = { T::ValueOne[Is] ... }; };
Don't forget the following line, outside the struct
template <typename T, std::size_t ... Is>
constexpr char ValueOneHolder<T, std::index_sequence<Is...>>::Value[];
The following is a full C++14 compiling example
#include <utility>
#include <iostream>
struct Base
{
static constexpr char ValueOne[] = "One";
static constexpr char ValueTwo[] = "Two";
};
template <typename T, std::size_t N>
constexpr std::size_t getDim (T const (&)[N])
{ return N; }
template <typename T,
typename = std::make_index_sequence<getDim(T::ValueOne)>>
struct ValueOneHolder;
template <typename T, std::size_t ... Is>
struct ValueOneHolder<T, std::index_sequence<Is...>>
{ static constexpr char Value[] = { T::ValueOne[Is] ... }; };
template <typename T, std::size_t ... Is>
constexpr char ValueOneHolder<T, std::index_sequence<Is...>>::Value[];
int main()
{
std::cout << ValueOneHolder<Base>::Value << std::endl;
}
If you want a C++11, you can develop a substitute for std::make_index_sequence and std::index_sequence.
In this particular example you may declare Value as the following:
template <typename T>
struct ValueOneHolder {
static constexpr auto Value = T::ValueOne; // << How can one initialize this?
};
Please note, GCC will fail to link this example unless you switch to -std=c++17 or add the folloing lines in a source file.
constexpr char Base::ValueOne[];
constexpr char Base::ValueTwo[];
With C++14 it is also possible to make a constexpr copy of a constexpr string (or its substring), as shown in example below:
template<typename CharT, size_t Size>
struct basic_cestring {
using value_type = CharT;
template<size_t... I> constexpr
basic_cestring(const char* str, index_sequence<I...>)
: _data{str[I]...} {}
inline constexpr operator const CharT* () const { return _data; }
const CharT _data[Size + 1];
};
template<size_t Size>
struct cestring : public basic_cestring<char, Size> {
using index = make_index_sequence<Size>;
constexpr cestring(const char* str)
: basic_cestring<char, Size>(str, index{}) {}
};
I managed to solve a previous question about initializing a static char array, asked here: Initializing a static char based on template parameter
I don't like the need for a secondary function in my solution:
//static char arr[N] = {[0]='0', [1]='x', [N-1]='\0',};
// ideally want this, but not currently implemented in g++
template <char... chars>
struct zero_str {};
template <unsigned N, char... chars>
struct seq_gen { using type = typename seq_gen<N-1, '0', chars...>::type; };
template <char... chars>
struct seq_gen<0, chars...> { using type = zero_str<chars...>; };
template <size_t N>
struct zero_gen { using type = typename seq_gen<N-1, '0', '\0'>::type; };
template <>
struct zero_gen<0> { using type = zero_str<'\0'>; };
template<size_t N> using strsize = typename zero_gen<N>::type;
template<typename T, char... chars>
const char* n2hexHelper(T val, zero_str<chars...>)
{
thread_local static char hexstr[] = {'0', 'x', chars...};
/* convert to hex */
return hexstr;
};
template<typename T>
const char* n2hex(T val)
{
return n2hexHelper<T> (val, strsize<sizeof(T)*2>() );
}
int main()
{
std::cout << n2hex(1) << std::endl;
return EXIT_SUCCESS;
}
Instead, I'd prefer not to need the dummy variable passed to the helper function, and be able to do something like this:
template<typename T, char... chars>
const char* n2HexIdeal(T val)
{
thread_local static char hexstr[] = {'0', 'x', chars...}; //how to get chars... ?
/* convert to hex */
return hexstr;
}
I have two main questions. 1) is something like my ideal case possible with parameter pack expansions? Or is the only way to force the compiler to deduce my char... is to use it as a function parameter? 2) I'm not very familiar with template metaprogramming, so I was wondering if there are any glaring faults or idiomatic missteps with my above solution.
This is doable in C++14.
template<class=void, std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
return [](auto&& f)->decltype(auto) {
return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto index_over( std::integral_constant< std::size_t, N > ={} ) {
return index_over( std::make_index_sequence<N>{} );
}
These are two helper functions that allow you to expand a bunch of std::size_t compile-time values without a "custom" helper function at each point of use.
We can then use them:
template<typename T>
const char * toStr(T num)
{
thread_local static
auto str = index_over<sizeof(T)*3>()
([&](auto...Is)->std::array<char, 3+3*sizeof(T)> {
return {{ '0', 'x',
(void(Is),'0')...,
'\0'
}};
});
// do something with str
(void)num;
return str.data();
}
to generate and expand our parameter packs inline.
This requires auto variardic lambdas, which is why it doesn't work in C++11.
live example.
If you can accept that your helper function is a static method of a class/struct, you can use the partial specialization as follows
template <typename, typename>
struct n2hexH;
template <typename T, char ... Chs>
struct n2hexH<T, zero_str<Chs...>>
{
static char * func (T const & val)
{
thread_local static char hexstr[] = {'0', 'x', Chs...};
/* convert to hex */
return hexstr;
}
};
Now, your n2hex() function become
template<typename T>
const char* n2hex(T val)
{ return n2hexH<T, strsize<(sizeof(T)<<1U)>>::func(val); }
or you can tranform it in a macro
#define n2hex(X) n2hexH<decltype(X), strsize<(sizeof(X)<<1U)>>::func(X)
I try to implement a data structure that comprises multiple name-value pairs where values may differ in their type:
template< typename T >
struct name_value_pair
{
std::string name;
T value;
};
template< typename... Ts >
class tuple_of_name_value_pairs
{
public:
/* type of value */ get_value( std::string n )
{
// return the value that the element in
// _name_value_pairs with name "n" comprises
}
private:
std::tuple<Ts...> _name_value_pairs:
};
Unfortunately, I have no idea how to implement the get function.
A workaround would be to state names as integers instead of strings and use an implementation according to std::get but this no option here: the input type of get has to be a string.
Has anyone an idea?
Firstly have in mind you cannot do what you want directly. C++ is a strongly typed language so type of function result must be known at compile time. So of course if the string you pass to the getter is known at runtime you're not able to dispatch function at compile time to let compiler deduce appropriate result type. But when you accept that you need type-erasure to erase the getter result type you could make use of e.g. boost::variant to deal with your problem. C++14 example (using boost, since c++17 variant should be available in std):
#include <boost/variant.hpp>
#include <utility>
#include <iostream>
#include <tuple>
template< typename T >
struct name_value_pair
{
using type = T;
std::string name;
T value;
};
template <std::size_t N, class = std::make_index_sequence<N>>
struct getter;
template <std::size_t N, std::size_t... Is>
struct getter<N, std::index_sequence<Is...>> {
template <class Val, class Res>
void setRes(Val &val, Res &res, std::string &s) {
if (val.name == s)
res = val.value;
}
template <class Tup>
auto operator()(Tup &tuple_vals, std::string &s) {
boost::variant<typename std::tuple_element<Is, Tup>::type::type...> result;
int helper[] = { (setRes(std::get<Is>(tuple_vals), result, s), 1)... };
(void)helper;
return result;
}
};
template <std::size_t N, class = std::make_index_sequence<N>>
struct setter;
template <std::size_t N, std::size_t... Is>
struct setter<N, std::index_sequence<Is...>> {
template <class Val, class SVal>
std::enable_if_t<!std::is_same<SVal, typename Val::type>::value> setVal(Val &, std::string &, const SVal &) { }
template <class Val>
void setVal(Val &val, std::string &s, const typename Val::type &sval) {
if (val.name == s)
val.value = sval;
}
template <class Tup, class Val>
auto operator()(Tup &tuple_vals, std::string &s, const Val &val) {
int helper[] = { (setVal(std::get<Is>(tuple_vals), s, val), 1)... };
(void)helper;
}
};
template <class T, class Res>
using typer = Res;
template< typename... Ts >
class tuple_of_name_value_pairs
{
public:
auto get_value( std::string n )
{
return getter<sizeof...(Ts)>{}(_name_value_pairs, n);
}
template <class T>
void set_value( std::string n, const T& value) {
setter<sizeof...(Ts)>{}(_name_value_pairs, n , value);
}
void set_names(typer<Ts, std::string>... names) {
_name_value_pairs = std::make_tuple(name_value_pair<Ts>{names, Ts{}}...);
}
private:
std::tuple<name_value_pair<Ts>...> _name_value_pairs;
};
int main() {
tuple_of_name_value_pairs<int, float, double> t;
t.set_names("abc", "def", "ghi");
t.set_value("abc", 1);
t.set_value("def", 4.5f);
t.set_value("ghi", 5.0);
std::cout << t.get_value("def") << std::endl;
}
[live demo]
I'm sure you'll be able to optimise the code (e.g. make use of move semantics/perfect forwarding, etc.). This is only to present you how to get your implementation started.
I am thinking about following problem:
Let us have a merging function for merge arrays defined in following way:
// input is (const void*, size_t, const void*, size_t,...)
template<typename...ARGS>
MyArray Concatenation(ARGS...args)
And let us have couple of structs with static properties
struct A { static void* DATA; static size_t SIZE; };
struct B { static void* DATA; static size_t SIZE; };
struct C { static void* DATA; static size_t SIZE; };
I would like to have a method:
template<typename...ARGS>
MyArray AutoConcatenation();
Where ARGS should be structs with mentioned static interface.
Following methods should have the same output:
AutoConcatenation<A, B, C>();
Concatenation(A::DATA, A::SIZE, B::DATA, B::SIZE, C::DATA, C::SIZE);
My question is how to implement parameter pack expansion.
I tried:
// not working
template<typename...ARGS>
MyArray AutoConcatenation()
{
return Concatenation((ARGS::DATA, ARGS::SIZE)...);
}
What about expansions
ARGS::DATA... // Correct expansion of pointers
ARGS::SIZE... // Correct expansion of sizes
(ARGS::DATA, ARGS::SIZE)... // Seems to be expansion of sizes
Just info for advisors. I am looking for implementation of AutoConcatenation method, not for its redeclaration nor for redeclaration previous code, thank you.
A lazy solution using std::tuple:
make a tuple of DATA and SIZE for each element of the parameter pack,
flatten the list of tuples to one big tuple using std::tuple_cat,
apply the resulting tuple's elements to Concatenation by expanding a list of indexes in an std::index_sequence.
In the following code, the test harness is longer than the actual solution:
#include <cstddef>
#include <tuple>
#include <utility>
#include <iostream>
#include <typeinfo>
#include <type_traits>
struct MyArray { };
template<class... ARGS> MyArray Concatenation(ARGS... args)
{
// Just some dummy code for testing.
using arr = int[];
(void)arr{(std::cout << typeid(args).name() << ' ' << args << '\n' , 0)...};
return {};
}
struct A { static void* DATA; static std::size_t SIZE; };
struct B { static void* DATA; static std::size_t SIZE; };
struct C { static void* DATA; static std::size_t SIZE; };
// Also needed for testing.
void* A::DATA;
std::size_t A::SIZE;
void* B::DATA;
std::size_t B::SIZE;
void* C::DATA;
std::size_t C::SIZE;
// The useful stuff starts here.
template<class T, std::size_t... Is> MyArray concat_hlp_2(const T& tup, std::index_sequence<Is...>)
{
return Concatenation(std::get<Is>(tup)...);
}
template<class T> MyArray concat_hlp_1(const T& tup)
{
return concat_hlp_2(tup, std::make_index_sequence<std::tuple_size<T>::value>{});
}
template<class... ARGS> MyArray AutoConcatenation()
{
return concat_hlp_1(std::tuple_cat(std::make_tuple(ARGS::DATA, ARGS::SIZE)...));
}
int main()
{
AutoConcatenation<A, B, C>();
}
If you want to avoid std::tuple and std::tuple_cat (which can be heavy in terms of compile times), here's an alternative using indexes into arrays.
The testing code stays the same, this is just the juicy stuff:
template<std::size_t Size> const void* select(std::false_type, std::size_t idx,
const void* (& arr_data)[Size], std::size_t (&)[Size])
{
return arr_data[idx];
}
template<std::size_t Size> std::size_t select(std::true_type, std::size_t idx,
const void* (&)[Size], std::size_t (& arr_size)[Size])
{
return arr_size[idx];
}
template<std::size_t... Is> MyArray concat_hlp(std::index_sequence<Is...>,
const void* (&& arr_data)[sizeof...(Is) / 2], std::size_t (&& arr_size)[sizeof...(Is) / 2])
{
return Concatenation(select(std::bool_constant<Is % 2>{}, Is / 2, arr_data, arr_size)...);
}
template<class... ARGS> MyArray AutoConcatenation()
{
return concat_hlp(std::make_index_sequence<sizeof...(ARGS) * 2>{}, {ARGS::DATA...}, {ARGS::SIZE...});
}
Again a sequence of indexes twice the size of the original parameter pack, but we build separate arrays of DATA and SIZE and then use tag dispatching to select elements from one or the other depending on the parity of the current index.
This may not look as nice as the previous code, but it doesn't involve any template recursion (std::make_index_sequence is implemented using compiler intrinsics in modern compilers as far as I know) and cuts down on the number of template instantiations, so it should be faster to compile.
The select helper can be made constexpr by using arrays of pointers to the static members, but this turns out to be unnecessary in practice. I've tested MSVC 2015 U2, Clang 3.8.0 and GCC 6.1.0 and they all optimize this to a direct call to Concatenation (just like for the tuple-based solution).
I think the following is more elegant, and it illustrates the common recursive unpacking pattern. Finally, it does not perform any voodoo with memory layouts and tries to be idiomatic as far as C++ generic programming.
#include <iostream>
#include <string>
using namespace std;
// Handle zero arguments.
template <typename T = string>
T concat_helper() { return T(); }
// Handle one pair.
template <typename T = string>
T concat_helper(const T &first, size_t flen) { return first; }
// Handle two or more pairs. Demonstrates the recursive unpacking pattern
// (very common with variadic arguments).
template <typename T = string, typename ...ARGS>
T concat_helper(const T &first, size_t flen,
const T &second, size_t slen,
ARGS ... rest) {
// Your concatenation code goes here. We're assuming we're
// working with std::string, or anything that has method length() and
// substr(), with obvious behavior, and supports the + operator.
T concatenated = first.substr(0, flen) + second.substr(0, slen);
return concat_helper<T>(concatenated, concatenated.length(), rest...);
}
template <typename T, typename ...ARGS>
T Concatenate(ARGS...args) { return concat_helper<T>(args...); }
template <typename T>
struct pack {
T data;
size_t dlen;
};
template <typename T>
T AutoConcatenate_helper() { return T(); }
template <typename T>
T AutoConcatenate_helper(const pack<T> *packet) {
return packet->data;
}
template <typename T, typename ...ARGS>
T AutoConcatenate_helper(const pack<T> *first, const pack<T> *second,
ARGS...rest) {
T concatenated = Concatenate<T>(first->data, first->dlen,
second->data, second->dlen);
pack<T> newPack;
newPack.data = concatenated;
newPack.dlen = concatenated.length();
return AutoConcatenate_helper<T>(&newPack, rest...);
}
template <typename T, typename ...ARGS>
T AutoConcatenate(ARGS...args) {
return AutoConcatenate_helper<T>(args...);
}
int main() {
pack<string> first;
pack<string> second;
pack<string> third;
pack<string> last;
first.data = "Hello";
first.dlen = first.data.length();
second.data = ", ";
second.dlen = second.data.length();
third.data = "World";
third.dlen = third.data.length();
last.data = "!";
last.dlen = last.data.length();
cout << AutoConcatenate<string>(&first, &second, &third, &last) << endl;
return 0;
}
We're neither changing the declaration of Concatenate<>(), nor that of AutoConcatenate<>(), as required. We're free to implement AutoConcatenate<>(), as we did, and we assume that there is some implementation of Concatenate<>() (we provided a simple one for a working example).
Here is possible solution:
enum Delimiters { Delimiter };
const void* findData(size_t count) { return nullptr; }
template<typename...ARGS>
const void* findData(size_t count, size_t, ARGS...args)
{
return findData(count, args...);
}
template<typename...ARGS>
const void* findData(size_t count, const void* data, ARGS...args)
{
return count ? findData(count - 1, args...) : data;
}
template<typename...ARGS>
MyArray reordered(size_t count, Delimiters, ARGS...args)
{
return Concatenate(args...);
}
template<typename...ARGS>
MyArray reordered(size_t count, const void* size, ARGS...args)
{
return reordered(count, args...);
}
template<typename...ARGS>
MyArray reordered(size_t count, size_t size, ARGS...args)
{
return reordered(count + 1, args..., findData(count, args...), size);
}
template<typename...ARGS>
MyArray AutoConcatenate()
{
return reordered(0, ARGS::LAYOUT_SIZE..., ARGS::LAYOUT..., Delimiter);
}
If you know more elegant way, please let me know.
EDIT
One little more elegant way is to keep function argument count as template parameter...