Related
I know that I can use SFINAE to disable generation of templated functions based on a condition, but that doesn't really work in this case. I want to initialize an array at compile-time that should contain values that matches a condition. Something like this:
template <std::size_t i, class ... Types, class ... Group>
constexpr auto fetch_match(const std::tuple<Group...>& candidates)
{
if constexpr (is_match<std::tuple<Group...>, i, Types...>())
{
auto& group = std::get<i>(candidates);
return group.template get<Types...>();
}
}
template <class ... Types, class ... Group, std::size_t ... indices>
constexpr auto get_matches(const std::tuple<Group...>& candidates, std::index_sequence<indices...>)
{
constexpr std::array views {
(fetch_match<indices, Types...>(candidates), ...),
};
return views;
}
I know the code above is wrong and doesn't compile. If the condition isn't filled, then I want the fold expression to not generate that function call. How would I do that?
This question might be an XY-problem, so here's a the problem in more detail.
I have a Registry that contains Groups of heterogeneous data. I want to be able to query all groups that contains the specified sub list of types. For example, for (const auto& view : registry.get<char, short, int>()) should yield an array with views of the groups that contain char, short and int. I've created a mcve below. The problem with the current code is that I have to first create the array and then copy the views, which I'd like to avoid.
#include <tuple>
#include <array>
#include <utility>
#include <type_traits>
#include <iostream>
template <typename T, typename... Ts>
constexpr bool contains = (std::is_same<T, Ts>{} || ...);
template <typename Subset, typename Set>
constexpr bool is_subset_of = false;
template <typename... Ts, typename... Us>
constexpr bool is_subset_of<std::tuple<Ts...>, std::tuple<Us...>> = (contains<Ts, Us...> && ...);
template <typename ... T>
struct View
{
const char* name_of_group; // For debugging.
std::tuple<T...> data;
};
template <typename ... Ts>
struct Group
{
using type_set = std::tuple<Ts...>;
static const char* name; // For debugging.
std::tuple<Ts...> data;
explicit Group(Ts... values) : data(values...) {}
template <typename ... Us>
[[nodiscard]] View<Us...> get() const noexcept
{
return { this->name, std::make_tuple(std::get<Us>(this->data)...) };
}
};
template <class Groups, std::size_t i, class ... Types>
constexpr bool is_match()
{
using group_type = std::tuple_element_t<i, Groups>;
bool match = is_subset_of<std::tuple<Types...>, typename group_type::type_set>;
return match;
}
template <std::size_t i, class ... Types, class ... Group, class Array>
constexpr void add_matches(const std::tuple<Group...>& candidates, Array& matches, std::size_t& index)
{
if constexpr (is_match<std::tuple<Group...>, i, Types...>())
{
auto& group = std::get<i>(candidates);
matches[index++] = group.template get<Types...>();
}
}
template <class ... Types, class ... Group, std::size_t ... indices>
constexpr auto get_matches(const std::tuple<Group...>& candidates, std::index_sequence<indices...>)
{
constexpr std::size_t size = (is_match<std::tuple<Group...>, indices, Types...>() + ... + 0);
std::array<View<Types...>, size> views {};
std::size_t index = 0;
(add_matches<indices, Types...>(candidates, views, index), ...);
return views;
}
template <typename ... Group>
class Registry
{
public:
explicit Registry(Group... groups) : groups(groups...) {}
template <typename ... T>
auto get()
{
constexpr auto indices = std::index_sequence_for<Group...>{};
return get_matches<T...>(this->groups, indices);
}
private:
std::tuple<Group...> groups;
};
using A = Group<char>;
using B = Group<char, short>;
using C = Group<char, short, int>;
using D = Group<char, short, int, long long>;
// Giving the classes names for debugging purposes.
template<> const char* A::name = "A";
template<> const char* B::name = "B";
template<> const char* C::name = "C";
template<> const char* D::name = "D";
int main()
{
auto registry = Registry(A{0}, B{1,1}, C{2,2,2}, D{3,3,3,3});
// Should yield an array of size 2 with View<char, short, int>,
// one from group C and one from Group D.
for (const auto& view : registry.get<char, short, int>())
{
std::cout << "View of group: " << view.name_of_group << std::endl;
std::cout << "char: " << int(std::get<char>(view.data)) << std::endl;
std::cout << "short: " << std::get<short>(view.data) << std::endl;
std::cout << "int: " << std::get<int>(view.data) << std::endl;
}
}
Trying the suggestion in the comments, the following code is as far as I got.
template <class Groups, std::size_t i, class ... Types>
constexpr bool is_match()
{
using group_type = std::tuple_element_t<i, Groups>;
bool match = is_subset_of<std::tuple<Types...>, typename group_type::type_set>;
return match;
}
template <class ... Types, class ... Group, std::size_t ... indices>
constexpr auto build_view_array(const std::tuple<Group...>& candidates, std::index_sequence<indices...>)
{
std::array views {
std::get<indices>(candidates).template get<Types...>()...
};
return views;
}
template <std::size_t i, class Groups, class TypeSet, std::size_t ... x>
constexpr auto get_matching_indices()
{
if constexpr (is_match<Groups, i, TypeSet>())
return std::index_sequence<x..., i>{};
else
return std::index_sequence<x...>{};
}
template <std::size_t i, std::size_t j, std::size_t ... rest, class Groups, class TypeSet, std::size_t ... x>
constexpr auto get_matching_indices()
{
if constexpr (is_match<Groups, i, TypeSet>())
return get_matching_indices<j, rest..., Groups, TypeSet, i, x...>();
else
return get_matching_indices<j, rest..., Groups, TypeSet, x...>();
}
template <class ... Types, class ... Group, std::size_t ... indices>
constexpr auto get_matches(const std::tuple<Group...>& candidates, std::index_sequence<indices...>)
{
constexpr auto matching_indices = get_matching_indices<indices..., std::tuple<Group...>, std::tuple<Types...>>();
constexpr auto views = build_view_array<Types...>(candidates, matching_indices);
return views;
}
It feels like it should work, but it won't compile due to the following error:
/Users/tedkleinbergman/Programming/ECS/temp.cpp:76:39: error: no matching function for call to 'get_matching_indices'
constexpr auto matching_indices = get_matching_indices<indices..., std::tuple<Group...>, std::tuple<Types...>>();
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/tedkleinbergman/Programming/ECS/temp.cpp:92:16: note: in instantiation of function template specialization 'get_matches<char, short, int, Group<char>, Group<char, short>, Group<char, short, int>, Group<char, short, int, long long> , 0, 1, 2, 3>' requested here
return get_matches<T...>(this->groups, indices);
^
/Users/tedkleinbergman/Programming/ECS/temp.cpp:118:38: note: in instantiation of function template specialization 'Registry<Group<char>, Group<char, short>, Group<char, short, int>, Group<char, short, int, long long> >::get<char, short, int>' requested here
for (const auto& view : registry.get<char, short, int>())
^
/Users/tedkleinbergman/Programming/ECS/temp.cpp:57:16: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'Groups'
constexpr auto get_matching_indices()
^
/Users/tedkleinbergman/Programming/ECS/temp.cpp:65:16: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'rest'
constexpr auto get_matching_indices()
^
1 error generated.
First, start with an index_sequence filter:
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, std::size_t...Js>
constexpr std::index_sequence<Is...,Js...> concatenate( std::index_sequence<Is...>, std::index_sequence<Js...> ) {
return {};
}
template <class Test>
constexpr auto filter_sequence(std::index_sequence<> sequence, Test test) {
return sequence;
}
template<std::size_t I0, std::size_t...Is, class Test>
constexpr auto filter_sequence( std::index_sequence<I0, Is...>, Test test )
{
constexpr auto tail = filter_sequence( std::index_sequence<Is...>{}, test );
if constexpr ( test(index<I0>) ) {
return concatenate( std::index_sequence<I0>{}, tail );
} else {
return tail;
}
}
we then use these primitives.
template <class Group, class ... Types>
constexpr auto get_match_indexes()
{
constexpr auto test = [](auto I){ return is_match<Group, I, Types...>(); };
constexpr auto indexes = std::make_index_sequence< std::tuple_size_v<Group> >{};
constexpr auto retval = filter_sequence( indexes, test );
return retval;
}
template<class ... Types, class Group, std::size_t...Is>
std::array<sizeof...Is, View<Types...>> get_matches(const Group& candidates, std::index_sequence<Is...> ) {
return {{
std::get<Is>(candidates).template get<Types...>(), ...
}};
}
template<class ... Types, class Group>
std::array<sizeof...Is, View<Types...>> get_matches(const Group& candidates ) {
return get_matches<Types...>( candidates, get_match_indexes<Group, Types...>() );
}
or something like that.
Note that some compilers may need to replace is_match<Group, I, Types...>() with is_match<Group, decltype(I)::value, Types...>().
There may be typos. This uses c++17 at the least.
filter_sequence uses O(n^2) template symbol length and O(n) recursive template instantiation depth. It can be improved to O(n lg n) length and O(lg n) depth with a tricky code; basically, you need to split Is... into As... and Bs... down the middle and recurse that way.
Here is a log-depth split of an index sequence:
template<class A, class B>
struct two_things {
A a;
B b;
};
template<class A, class B>
two_things(A,B)->two_things<A,B>;
template<class Seq>
constexpr auto split_sequence( index_t<0>, Seq seq ) {
return two_things{ std::index_sequence<>{}, seq };
}
template<std::size_t I0, std::size_t...Is>
constexpr auto split_sequence( index_t<1>, std::index_sequence<I0, Is...> seq ) {
return two_things{ std::index_sequence<I0>{}, std::index_sequence<Is...>{} };
}
template<std::size_t N, class Seq>
constexpr auto split_sequence( index_t<N>, Seq seq ) {
constexpr auto step1 = split_sequence( constexpr_index<N/2>, seq );
constexpr auto step2 = split_sequence( constexpr_index<N-N/2>, step1.b );
return two_things{ concatenate(step1.a, step2.a), step2.b };
}
template<std::size_t...Is>
constexpr auto halve_sequence( std::index_sequence<Is...> seq ) {
return split( index< (sizeof...(Is)) / 2u >, seq );
}
(two_things exists as a many-many-many times lighter tuple or pair than the std one).
That in turn lets you improve filter sequence.
template<std::size_t I, class Test>
constexpr auto filter_sequence( std::index_sequence<I> seq, Test test )
{
if constexpr ( test(constexpr_index<I>) ) {
return seq;
} else {
return std::index_sequence<>{};
}
}
template<std::size_t...Is, class Test>
constexpr auto filter_sequence( std::index_sequence<Is...> seq, Test test )
{
constexpr auto split = halve_sequence( seq );
constexpr auto head = filter_sequence( split.a, test );
constexpr auto tail = filter_sequence( split.b, test );
return concatenate(head, tail);
}
this version should compile faster and use less memory, especially for large numbers of elements. But you should start with the simpler one above, because (as I noted) there are probably plenty of tpyos.
Live example.
I have implemented a simple fold function in C++ that accepts a lambda, and can fold multiple vectors at the same time at compile time. I am wondering if it could be simplified in some manner (I have provided both a recursive version and an iteratively recursive version - I am unsure which should have better performance): https://godbolt.org/z/39pW81
Performance optimizations are also welcome - in that regard is any of the two approaches faster?
template<int I, typename type_identity, typename type_head, int N, typename ...type_tail, int ...N_tail, typename Function>
auto foldHelperR(Function&& func, const type_identity& id, const tvecn<type_head, N>& head, const tvecn<type_tail, N_tail>&... tail)
{
if constexpr (I>0)
{
return func(foldHelperR<I-1>(std::forward<Function>(func), id, head, tail...), head[I], tail[I]...);
}
else
{
return func(id, head[0], tail[0]...);
}
}
template<int I, typename type_identity, typename type_head, int N, typename ...type_tail, int ...N_tail, typename Function>
auto foldHelperI(Function&& func, const type_identity id, const tvecn<type_head, N>& head, const tvecn<type_tail, N_tail>&... tail)
{
if constexpr (I<N-1)
{
return foldHelperI<I+1>(std::forward<Function>(func), func(id, head[I], tail[I]...), head, tail...);
}
else
{
return func(id, head[N-1], tail[N-1]...);
}
}
template<typename type_identity, typename type_head, int N_head, typename ...type_tail, int ...N_tail, typename Function = void (const type_identity&, const type_head&, const type_tail&...)>
constexpr auto fold(Function&& func, const type_identity& id, const tvecn<type_head, N_head>& head, const tvecn<type_tail, N_tail>&... tail)
{
static_assert(std::is_invocable_v<Function, const type_identity&, const type_head&, const type_tail &...>,
"The function cannot be invoked with these zip arguments (possibly wrong argument count).");
static_assert(all_equal_v<N_head, N_tail...>, "Vector sizes must match.");
//return foldHelperR<N_head-1>(std::forward<Function>(func), id, head, tail...);
return foldHelperI<0>(std::forward<Function>(func), id, head, tail...);
}
int main()
{
tvecn<int,3> a(1,2,3);
return fold([](auto x, auto y, auto z) {return x+y+z;}, 0, a, a);
}
and can fold multiple vectors at the same time at compile time
Not exactly: if you want to operate compile-time
(1) you have to define constexpr the tvecn constructor and
(2) you have to define constexpr the foldhelper function and
(3) you have to declare constexpr a
// VVVVVVVVV
constexpr tvecn<int,3> a(1,2,3);
(4) you have to place the result of fold in a constexpr variable (or, more generally speaking, in a place where the value is required compile time, as the size field of a C-style array, or a template value parameter, or a static_assert() test)
constexpr auto f = fold([](auto x, auto y, auto z) {return x+y+z;},
0, a, a);
I am wondering if it could be simplified in some manner
Sure.
First of all: if you can, avoid to reinventing the weel: your tvecn is a simplified version of std::array.
Suggestion: use std::array (if you can obviously)
Second: you tagged C++17 so you can use folding
Suggestion: use it also for all_equal
template <auto V0, auto ... Vs>
struct all_equal : public std::bool_constant<((V0 == Vs) && ...)>
{ };
template<auto ...N_pack>
constexpr bool all_equal_v = all_equal<N_pack...>::value;
More in general: when you have to define a custom type traits that has to provide a number, inherit (if possible) from std::integral_constant (or std::bool_constant, or std::true_type, or std::false_type: all std::integral_constant specializations). So you automatically inherit all std::integral_constant facilities.
Third: almost all C++ standard uses std::size_t, not int, for sizes.
Suggestion: when you have to do with sizes, use std::size_t, not int. This way you can avoid a lot of annoying troubles.
Fourth: from main() you should return only EXIT_SUCCESS (usually zero) or EXIT_FAILURE (usually 1)
Suggestion: avoid things as
return fold([](auto x, auto y, auto z) {return x+y+z;}, 0, a, a);
Fifth: never underestimate the power of the comma operator.
Suggestion: avoid recursion at all and use template folding also for the helper function; by example
template <std::size_t ... Is, typename F, typename T, typename ... As>
constexpr auto foldHelperF (std::index_sequence<Is...>,
F const & f, T id, As const & ... arrs)
{ return ( ..., (id = [&](auto i){ return f(id, arrs[i]...); }(Is))); }
that you can call as follows from fold()
return foldHelperF(std::make_index_sequence<N_head>{},
std::forward<Function>(func),
id, head, tail...);
The following is a full compiling, and simplified, example
#include <array>
#include <utility>
#include <iostream>
#include <type_traits>
template <auto V0, auto ... Vs>
struct all_equal : public std::bool_constant<((V0 == Vs) && ...)>
{ };
template<auto ...N_pack>
constexpr bool all_equal_v = all_equal<N_pack...>::value;
template <std::size_t ... Is, typename F, typename T, typename ... As>
constexpr auto foldHelperF (std::index_sequence<Is...>,
F const & f, T id, As const & ... arrs)
{ return ( ..., (id = [&](auto i){ return f(id, arrs[i]...); }(Is))); }
template <typename type_identity, typename type_head, std::size_t N_head,
typename ...type_tail, std::size_t ...N_tail,
typename Function = void (type_identity const &,
type_head const &,
type_tail const & ...)>
constexpr auto fold (Function && func, type_identity const & id,
std::array<type_head, N_head> const & head,
std::array<type_tail, N_tail> const & ... tail)
{
static_assert( std::is_invocable_v<Function, const type_identity&,
const type_head&, const type_tail &...>,
"The function cannot be invoked with these zip arguments"
" (possibly wrong argument count).");
static_assert( all_equal_v<N_head, N_tail...>,
"Vector sizes must match.");
return foldHelperF(std::make_index_sequence<N_head>{},
std::forward<Function>(func),
id, head, tail...);
}
int main()
{
constexpr std::array<int, 3u> b{2, 5, 7};
constexpr auto f = fold([](auto x, auto y, auto z) {return x+y+z;},
0, b, b);
std::cout << f << std::endl;
}
With Fold expression, it might be:
template <typename F, typename Init, std::size_t... Is, typename... Arrays>
constexpr auto fold_impl(F&& f, Init init, std::index_sequence<Is...>, Arrays&&... arrays)
{
auto l = [&](Init init, std::size_t i){ return f(init, arrays[i]...); };
return ((init = l(init, Is)), ...);
}
template <typename F, typename Init, typename Array, typename ... Arrays>
constexpr auto fold(F&& f, Init init, Array&& array, Arrays&&... arrays)
{
static_assert(((arrays.size() == array.size()) && ...));
return fold_impl(f, init, std::make_index_sequence<array.size()>{}, array, arrays...);
}
Demo
I want to write a template function that writes tables to HDF5 files.
The signature should look similar to
template<typename record> void writeTable(const std::vector<record>& data);
where record is a struct, or
template<typename... elements>
void writeTable(const std::vector<std::tuple<elements...>>& data);
The actual implementation would have more parameters to determine the destionation, etc.
To write the data I need to define a HDF5 compound type, which contains the name and the offset of the members. Usually you would use the HOFFSET macro the get the field offset, but as I don't know the struct fields beforehand I can't do that.
What I tried so far was constructing a struct type from the typename pack. The naive implementation did not have standard layout, but the implementation here does. All that's left is get the offsets of the members. I would like to expand the parameter pack into an initializer list with the offsets:
#include <vector>
template<typename... members> struct record {};
template<typename member, typename... members> struct record<member, members...> :
record<members...> {
record(member m, members... ms) : record<members...>(ms...), tail(m) {}
member tail;
};
template<typename... Args> void
make_table(const std::string& name, const std::vector<record<Args...>>& data) {
using record_type = record<Args...>;
std::vector<size_t> offsets = { get_offset(record_type,Args)... };
}
int main() {
std::vector<record<int, float>> table = { {1, 1.0}, {2, 2.0} };
make_table("table", table);
}
Is there a possible implementation for get_offset? I would think not, because in the case of record<int, int> it would be ambiguous. Is there another way to do it?
Or is there any other way I could approach this problem?
Calculating offsets is quite simple. Given a tuple with types T0, T1 ... TN. The offset of T0 is 0 (as long as you use alignas(T0) on your char array. The offset of T1 is the sizeof(T0) rounded up to alignof(T1).
In general, the offset of TB (which comes after TA) is round_up(offset_of<TA>() + sizeof(TA), alignof(TB)).
Calculating the offsets of elements in a std::tuple could be done like this:
constexpr size_t roundup(size_t num, size_t multiple) {
const size_t mod = num % multiple;
return mod == 0 ? num : num + multiple - mod;
}
template <size_t I, typename Tuple>
struct offset_of {
static constexpr size_t value = roundup(
offset_of<I - 1, Tuple>::value + sizeof(std::tuple_element_t<I - 1, Tuple>),
alignof(std::tuple_element_t<I, Tuple>)
);
};
template <typename Tuple>
struct offset_of<0, Tuple> {
static constexpr size_t value = 0;
};
template <size_t I, typename Tuple>
constexpr size_t offset_of_v = offset_of<I, Tuple>::value;
Here's a test suite. As you can see from the first test, the alignment of elements is taken into account.
static_assert(offset_of_v<1, std::tuple<char, long double>> == 16);
static_assert(offset_of_v<2, std::tuple<char, char, long double>> == 16);
static_assert(offset_of_v<3, std::tuple<char, char, char, long double>> == 16);
static_assert(offset_of_v<4, std::tuple<char, char, char, char, long double>> == 16);
static_assert(offset_of_v<0, std::tuple<int, double, int, char, short, long double>> == 0);
static_assert(offset_of_v<1, std::tuple<int, double, int, char, short, long double>> == 8);
static_assert(offset_of_v<2, std::tuple<int, double, int, char, short, long double>> == 16);
static_assert(offset_of_v<3, std::tuple<int, double, int, char, short, long double>> == 20);
static_assert(offset_of_v<4, std::tuple<int, double, int, char, short, long double>> == 22);
static_assert(offset_of_v<5, std::tuple<int, double, int, char, short, long double>> == 32);
I hardcoded the offsets in the above tests. The offsets are correct if the following tests succeed.
static_assert(sizeof(char) == 1 && alignof(char) == 1);
static_assert(sizeof(short) == 2 && alignof(short) == 2);
static_assert(sizeof(int) == 4 && alignof(int) == 4);
static_assert(sizeof(double) == 8 && alignof(double) == 8);
static_assert(sizeof(long double) == 16 && alignof(long double) == 16);
std::tuple seems to store it's elements sequentially (without sorting them to optimize padding). That's proven by the following tests. I don't think the standard requires std::tuple to be implemented this way so I don't think the following tests are guaranteed to succeed.
template <size_t I, typename Tuple>
size_t real_offset(const Tuple &tup) {
const char *base = reinterpret_cast<const char *>(&tup);
return reinterpret_cast<const char *>(&std::get<I>(tup)) - base;
}
int main(int argc, char **argv) {
using Tuple = std::tuple<int, double, int, char, short, long double>;
Tuple tup;
assert((offset_of_v<0, Tuple> == real_offset<0>(tup)));
assert((offset_of_v<1, Tuple> == real_offset<1>(tup)));
assert((offset_of_v<2, Tuple> == real_offset<2>(tup)));
assert((offset_of_v<3, Tuple> == real_offset<3>(tup)));
assert((offset_of_v<4, Tuple> == real_offset<4>(tup)));
assert((offset_of_v<5, Tuple> == real_offset<5>(tup)));
}
Now that I've gone to all of this effort, would that real_offset function suit your needs?
This is a minimal implementation of a tuple that accesses a char[] with offset_of. This is undefined behavior though because of the reinterpret_cast. Even though I'm constructing the object in the same bytes and accessing the object in the same bytes, it's still UB. See this answer for all the standardese. It will work on every compiler you can find but it's UB so just use it anyway. This tuple is standard layout (unlike std::tuple). If the elements of your tuple are all trivially copyable, you can remove the copy and move constructors and replace them with memcpy.
template <typename... Elems>
class tuple;
template <size_t I, typename Tuple>
struct tuple_element;
template <size_t I, typename... Elems>
struct tuple_element<I, tuple<Elems...>> {
using type = std::tuple_element_t<I, std::tuple<Elems...>>;
};
template <size_t I, typename Tuple>
using tuple_element_t = typename tuple_element<I, Tuple>::type;
template <typename Tuple>
struct tuple_size;
template <typename... Elems>
struct tuple_size<tuple<Elems...>> {
static constexpr size_t value = sizeof...(Elems);
};
template <typename Tuple>
constexpr size_t tuple_size_v = tuple_size<Tuple>::value;
constexpr size_t roundup(size_t num, size_t multiple) {
const size_t mod = num % multiple;
return mod == 0 ? num : num + multiple - mod;
}
template <size_t I, typename Tuple>
struct offset_of {
static constexpr size_t value = roundup(
offset_of<I - 1, Tuple>::value + sizeof(tuple_element_t<I - 1, Tuple>),
alignof(tuple_element_t<I, Tuple>)
);
};
template <typename Tuple>
struct offset_of<0, Tuple> {
static constexpr size_t value = 0;
};
template <size_t I, typename Tuple>
constexpr size_t offset_of_v = offset_of<I, Tuple>::value;
template <size_t I, typename Tuple>
auto &get(Tuple &tuple) noexcept {
return *reinterpret_cast<tuple_element_t<I, Tuple> *>(tuple.template addr<I>());
}
template <size_t I, typename Tuple>
const auto &get(const Tuple &tuple) noexcept {
return *reinterpret_cast<tuple_element_t<I, Tuple> *>(tuple.template addr<I>());
}
template <typename... Elems>
class tuple {
alignas(tuple_element_t<0, tuple>) char storage[offset_of_v<sizeof...(Elems), tuple<Elems..., char>>];
using idx_seq = std::make_index_sequence<sizeof...(Elems)>;
template <size_t I>
void *addr() {
return static_cast<void *>(&storage + offset_of_v<I, tuple>);
}
template <size_t I, typename Tuple>
friend auto &get(const Tuple &) noexcept;
template <size_t I, typename Tuple>
friend const auto &get(Tuple &) noexcept;
template <size_t... I>
void default_construct(std::index_sequence<I...>) {
(new (addr<I>()) Elems{}, ...);
}
template <size_t... I>
void destroy(std::index_sequence<I...>) {
(get<I>(*this).~Elems(), ...);
}
template <size_t... I>
void move_construct(tuple &&other, std::index_sequence<I...>) {
(new (addr<I>()) Elems{std::move(get<I>(other))}, ...);
}
template <size_t... I>
void copy_construct(const tuple &other, std::index_sequence<I...>) {
(new (addr<I>()) Elems{get<I>(other)}, ...);
}
template <size_t... I>
void move_assign(tuple &&other, std::index_sequence<I...>) {
(static_cast<void>(get<I>(*this) = std::move(get<I>(other))), ...);
}
template <size_t... I>
void copy_assign(const tuple &other, std::index_sequence<I...>) {
(static_cast<void>(get<I>(*this) = get<I>(other)), ...);
}
public:
tuple() noexcept((std::is_nothrow_default_constructible_v<Elems> && ...)) {
default_construct(idx_seq{});
}
~tuple() {
destroy(idx_seq{});
}
tuple(tuple &&other) noexcept((std::is_nothrow_move_constructible_v<Elems> && ...)) {
move_construct(other, idx_seq{});
}
tuple(const tuple &other) noexcept((std::is_nothrow_copy_constructible_v<Elems> && ...)) {
copy_construct(other, idx_seq{});
}
tuple &operator=(tuple &&other) noexcept((std::is_nothrow_move_assignable_v<Elems> && ...)) {
move_assign(other, idx_seq{});
return *this;
}
tuple &operator=(const tuple &other) noexcept((std::is_nothrow_copy_assignable_v<Elems> && ...)) {
copy_assign(other, idx_seq{});
return *this;
}
};
Alternatively, you could use this function:
template <size_t I, typename Tuple>
size_t member_offset() {
return reinterpret_cast<size_t>(&std::get<I>(*static_cast<Tuple *>(nullptr)));
}
template <typename Member, typename Class>
size_t member_offset(Member (Class::*ptr)) {
return reinterpret_cast<size_t>(&(static_cast<Class *>(nullptr)->*ptr));
}
template <auto MemPtr>
size_t member_offset() {
return member_offset(MemPtr);
}
Once again, this is undefined behavior (because of the nullptr dereference and the reinterpret_cast) but it will work as expected with every major compiler. The function cannot be constexpr (even though member offset is a compile-time calculation).
Not sure to understand what do you exactly want but... what about using recursion based on a index sequence (starting from C++14) something as follows?
#include <vector>
#include <utility>
#include <iostream>
template <typename... members>
struct record
{ };
template <typename member, typename... members>
struct record<member, members...> : record<members...>
{
record (member m, members... ms) : record<members...>(ms...), tail(m)
{ }
member tail;
};
template <std::size_t, typename, std::size_t = 0u>
struct get_offset;
template <std::size_t N, typename A0, typename ... As, std::size_t Off>
struct get_offset<N, record<A0, As...>, Off>
: public get_offset<N-1u, record<As...>, Off+sizeof(A0)>
{ };
template <typename A0, typename ... As, std::size_t Off>
struct get_offset<0u, record<A0, As...>, Off>
: public std::integral_constant<std::size_t, Off>
{ };
template <typename... Args, std::size_t ... Is>
auto make_table_helper (std::string const & name,
std::vector<record<Args...>> const & data,
std::index_sequence<Is...> const &)
{ return std::vector<std::size_t>{ get_offset<Is, record<Args...>>::value... }; }
template <typename... Args>
auto make_table (std::string const & name,
std::vector<record<Args...>> const & data)
{ return make_table_helper(name, data, std::index_sequence_for<Args...>{}); }
int main ()
{
std::vector<record<int, float>> table = { {1, 1.0}, {2, 2.0} };
auto v = make_table("table", table);
for ( auto const & o : v )
std::cout << o << ' ';
std::cout << std::endl;
}
Unfortunately isn't an efficient solution because the last value is calculated n-times.
I'm struggling with some template programming and I hope you can give me some help. I coded a C++11 interface that, given some structs like:
struct Inner{
double a;
};
struct Outer{
double x, y, z, r;
Inner in;
};
Implements a getter/setter to the real data that is customized to the specified struct members:
MyData<Outer, double, &Outer::x,
&Outer::y,
&Outer::z,
&Outer::in::a //This one is not working
> state();
Outer foo = state.get();
//...
state.set(foo);
I managed to implement this for simple structs in the following way:
template <typename T, typename U, U T::* ... Ms>
class MyData{
std::vector<U *> var;
public:
explicit MyData();
void set(T const& var_);
T get() const;
};
template <typename T, typename U, U T::* ... Ms>
MyData<T, U, Ms ... >::Struct():var(sizeof...(Ms))
{
}
template <typename T, typename U, U T::* ... Ms>
void MyData<T, U, Ms ...>::set(T const& var_){
unsigned i = 0;
for ( auto&& d : {Ms ...} ){
*var[i++] = var_.*d;
}
}
template <typename T, typename U, U T::* ... Ms>
T MyData<T, U, Ms ...>::get() const{
T var_;
unsigned i = 0;
for ( auto&& d : {Ms ...} ){
var_.*d = *var[i++];
}
return var_;
}
But it fails when I pass a member of a nested struct. Ideally, I'd like to implement a generic pointer to member type that allows me to be compatible with several levels of scope resolutions. I found this approach, but I'm not sure if this should be applied to my problem or if there exists some implementation ready to use. Thanks in advance!
Related posts:
Implicit template parameters
Pointer to inner struct
You might wrap member pointer into struct to allow easier chaining:
template <typename...> struct Accessor;
template <typename T, typename C, T (C::*m)>
struct Accessor<std::integral_constant<T (C::*), m>>
{
const T& get(const C& c) { return c.*m; }
T& get(C& c) { return c.*m; }
};
template <typename T, typename C, T (C::*m), typename ...Ts>
struct Accessor<std::integral_constant<T (C::*), m>, Ts...>
{
auto get(const C& c) -> decltype(Accessor<Ts...>().get(c.*m))
{ return Accessor<Ts...>().get(c.*m); }
auto get(C& c) -> decltype(Accessor<Ts...>().get(c.*m))
{ return Accessor<Ts...>().get(c.*m); }
};
template <typename T, typename U, typename ...Ts>
class MyData
{
std::vector<U> vars{sizeof...(Ts)};
template <std::size_t ... Is>
T get(std::index_sequence<Is...>) const
{
T res;
((Ts{}.get(res) = vars[Is]), ...); // Fold expression C++17
return res;
}
template <std::size_t ... Is>
void set(std::index_sequence<Is...>, T const& t)
{
((vars[Is] = Ts{}.get(t)), ...); // Fold expression C++17
}
public:
MyData() = default;
T get() const { return get(std::index_sequence_for<Ts...>()); }
void set(const T& t) { return set(std::index_sequence_for<Ts...>(), t); }
};
With usage similar to
template <auto ...ms> // C++17 too
using Member = Accessor<std::integral_constant<decltype(ms), ms>...>;
MyData<Outer, double, Member<&Outer::x>,
Member<&Outer::y>,
Member<&Outer::z>,
Member<&Outer::in, &Inner::a>
> state;
std::index_sequence is C++14 but can be implemented in C++11.
Folding expression from C++17 can be simulated too in C++11.
typename <auto> (C++17) should be replaced by typename <typename T, T value>.
Demo
A generalization of a member pointer is a function that can map T to X& at compile time.
In c++17 it isn't hard to wire things up thanks to auto. In c++11 it gets harder. But the basic idea is that you don't actually pass member pointers, you pass types, and those types know how to take your class and get a reference out of them.
template<class T, class D, class...Fs>
struct MyData {
std::array<D*, sizeof...(Fs)> var = {};
explicit MyData()=default;
void set(T const& var_) {
var = {{ Fs{}(std::addressof(var_))... }};
}
T get() {
T var_;
std::size_t index = 0;
using discard=int[];
(void)discard{ 0, (void(
*Fs{}(std::addressof(var_)) = *var[index++]
),0)... };
return var_;
}
};
it remains to write a utility that makes writing the Fs... easy for the member pointer case
template<class X, X M>
struct get_ptr_to_member_t;
template<class T, class D, D T::* M>
struct get_ptr_to_member_t< D T::*, M > {
D const* operator()( T const* t )const{
return std::addressof( t->*M );
}
};
#define TYPE_N_VAL(...) \
decltype(__VA_ARGS__), __VA_ARGS__
#define MEM_PTR(...) get_ptr_to_member_t< TYPE_N_VAL(__VA_ARGS__) >
now the basic case is
MyData< Outer, double, MEM_PTR(&Outer::x), MEM_PTR(&Outer::y) >
The more complex case can now be handled.
An approach would be to teach get_ptr_to_member to compose. This is annoying work, but nothing fundamental. Arrange is so that decltype(ptr_to_member_t * ptr_to_member_t) returns a type that instances right, applies it, then takes that pointer and runs the left hand side on it.
template<class First, class Second>
struct composed;
template<class D>
struct composes {};
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
template<class First, class Second>
struct composed:composes<composed<First, Second>> {
template<class In>
auto operator()(In&& in) const
RETURNS( Second{}( First{}( std::forward<In>(in) ) ) )
};
template<class First, class Second>
composed<First, Second> operator*( composes<Second> const&, composes<First> const& ) {
return {};
}
then we upgrade:
template<class X, X M>
struct get_ptr_to_member_t;
template<class T, class D, D T::* M>
struct get_ptr_to_member_t< D T::*, M >:
composes<get_ptr_to_member_t< D T::*, M >>
{
D const* operator()( T const* t )const{
return std::addressof( t->*M );
}
};
and now * composes them.
MyData<TestStruct, double, MEM_PTR(&Outer::x),
MEM_PTR(&Outer::y),
MEM_PTR(&Outer::z),
decltype(MEM_PTR(&Inner::a){} * MEM_PTR(&Outer::in){})
> state();
answre probably contains many typos, but design is sound.
In c++17 most of the garbage evaporates, like the macros.
I would use lambda approach to implement similar functionalities in C++17(C++14 is also ok, just change the fold expression):
auto access_by() {
return [] (auto &&t) -> decltype(auto) {
return decltype(t)(t);
};
}
template<class Ptr0, class... Ptrs>
auto access_by(Ptr0 ptr0, Ptrs... ptrs) {
return [=] (auto &&t) -> decltype(auto) {
return access_by(ptrs...)(decltype(t)(t).*ptr0);
};
}
auto data_assigner_from = [] (auto... accessors) {
return [=] (auto... data) {
return [accessors..., data...] (auto &&t) {
((accessors(decltype(t)(t)) = data), ...);
};
};
};
Let's see how to use these lambdas:
struct A {
int x, y;
};
struct B {
A a;
int z;
};
access_by function can be used like:
auto bax_accessor = access_by(&B::a, &A::x);
auto bz_accessor = access_by(&B::z);
Then for B b;, bax_accessor(b) is b.a.x; bz_accessor(b) is b.z. Value category is also preserved, so you can assign: bax_accessor(b) = 4.
data_assigner_from() will construct an assigner to assign a B instance with given accessors:
auto data_assigner = data_assigner_from(
access_by(&B::a, &A::x),
access_by(&B::z)
);
data_assigner(12, 3)(b);
assert(b.z == 3 && b.a.x == 12);
I have a variadic template function foo():
template <typename... Args>
void foo(Args &&... args);
This function is intended to be invoked with all arguments of size_t. I can enforce that using some metaprogramming. I need to take the resulting list of arguments two at a time and put them into a container of std::pair<size_t, size_t>. Conceptually, something like:
std::vector<std::pair<size_t, size_t> > = {
std::make_pair(args[0], args[1]),
std::make_pair(args[2], args[3]), ...
};
Is there a straightforward way to do this? I know that by pack expansion, I could put the arguments into a flat container, but is there a way to group them two by two into std::pair objects at the same time?
Indexing into packs isn't really doable (yet?), but indexing into tuples is. Just stick everything into a tuple first, and then pull everything back out as you go. Since everything's a size_t, we can just copy:
template <size_t... Is, class Tuple>
std::vector<std::pair<size_t, size_t>>
foo_impl(std::index_sequence<Is...>, Tuple tuple) {
return std::vector<std::pair<size_t, size_t> >{
std::make_pair(std::get<2*Is>(tuple), std::get<2*Is+1>(tuple))...
};
}
template <typename... Args>
void foo(Args... args)
{
auto vs = foo_impl(std::make_index_sequence<sizeof...(Args)/2>{},
std::make_tuple(args...));
// ...
}
Suppose you are allowed to refactor your logic into an internal helper function:
template <typename ...Args>
void foo(Args &&... args)
{
foo_impl(std::make_index_sequence<sizeof...(Args) / 2>(),
std::forward<Args>(args)...);
}
Now we can operate on the argument pack index by index:
template <std::size_t ...I, typename ...Args>
void foo_impl(std::index_sequence<I...>, Args &&... args)
{
std::vector<std::pair<std::size_t, std::size_t>> v =
{ GetPair(std::integral_constant<std::size_t, I>(), args...)... };
}
It remains to implement the pair extractor:
template <typename A, typename B, typename ...Tail>
std::pair<std::size_t, std::size_t> GetPair(std::integral_constant<std::size_t, 0>,
A a, B b, Tail ... tail)
{
return { a, b };
}
template <std::size_t I, typename A, typename B, typename ...Tail>
std::pair<std::size_t, std::size_t> GetPair(std::integral_constant<std::size_t, I>,
A a, B b, Tail ... tail)
{
return GetPair<I - 1>(tail...);
}
With range-v3, you may do
template <typename... Args>
void foo(Args&&... args)
{
std::initializer_list<std::size_t> nbs = {static_cast<std::size_t>(args)...};
const auto pair_view =
ranges::view::zip(nbs | ranges::view::stride(2),
nbs | ranges::view::drop(1) | ranges::view::stride(2));
// And possibly
std::vector<std::pair<std::size_t, std::size_t>> pairs = pair_view;
// ...
}
Demo
Someone (cough #Barry cough) said that indexing into packs isn't possible.
This is C++. Impossible means we just haven't written it yet.
template<std::size_t I> struct index_t:std::integral_constant<std::size_t, I> {
using std::integral_constant<std::size_t, I>::integral_constant;
template<std::size_t J>
constexpr index_t<I+J> operator+( index_t<J> ) const { return {}; }
template<std::size_t J>
constexpr index_t<I-J> operator-( index_t<J> ) const { return {}; }
template<std::size_t J>
constexpr index_t<I*J> operator*( index_t<J> ) const { return {}; }
template<std::size_t J>
constexpr index_t<I/J> operator/( index_t<J> ) const { return {}; }
};
template<std::size_t I>
constexpr index_t<I> index{};
template<std::size_t B>
constexpr index_t<1> exponent( index_t<B>, index_t<0> ) { return {}; }
template<std::size_t B, std::size_t E>
constexpr auto exponent( index_t<B>, index_t<E> ) {
return index<B> * exponent( index<B>, index<E-1> );
}
template<std::size_t N>
constexpr index_t<0> from_base(index_t<N>) { return {}; }
template<std::size_t N, std::size_t c>
constexpr index_t<c-'0'> from_base(index_t<N>, index_t<c>) { return {}; }
template<std::size_t N, std::size_t c0, std::size_t...cs>
constexpr auto from_base(index_t<N>, index_t<c0>, index_t<cs>...) {
return
from_base(index<N>, index<c0>) * exponent(index<N>, index<sizeof...(cs)>)
+ from_base(index<N>, index<cs>...)
;
}
template<char...cs>
constexpr auto operator""_idx(){
return from_base(index<10>, index<cs>...);
}
auto nth = [](auto index_in){
return [](auto&&...elems)->decltype(auto){
using std::get;
constexpr auto I= index<decltype(index_in){}>;
return get<I>(std::forward_as_tuple(decltype(elems)(elems)...));
};
};
Now we get:
using pair_vec = std::vector<std::pair<std::size_t, std::size_t>>;
template <typename... Args>
pair_vec foo(Args &&... args) {
return
index_over< sizeof...(args)/2 >()
([&](auto...Is)->pair_vec{
return {
{
nth( Is*2_idx )( decltype(args)(args)... ),
nth( Is*2_idx+1_idx )( decltype(args)(args)... )
}...
};
});
}
where we "directly" index into our parameter packs using compile time constant indexes.
live example.