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_
Related
i am trying to change the data type of all the elements into a nested C array, something like this.
const int a[2][3] = {
{1,2,3},
{4,5,6}
}
The arrays are "multidimensional", and i don't know how many dimension they have.
I figured out something like this:
template <class D, class T, unsigned S>
inline D& uniform(T (&t)[S]) {
D v[S];
for (int k = 0; k < S; k++) {
v[k] = D(t[k]);
}
return v;
}
auto b = uniform<float>( a );
However the previous code works (or at least it is supposed to work) only if a is 1D, is there a way to make it work over multidimensional C arrays?
So, here's one way of doing it:
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdio>
#include <type_traits>
// array_type_swap convert T[a][b][c][...] to Y[a][b][c][...] for arbitrary
// dimensions
template <class T, class Y>
struct array_type_swap {
using type = T;
};
template <class T, class Y, std::size_t N>
struct array_type_swap<T, std::array<Y, N>> {
using type = std::array<typename array_type_swap<T, Y>::type, N>;
};
template <class T, class Y, std::size_t N>
struct array_type_swap<T, Y[N]> {
using type = typename array_type_swap<T, std::array<Y, N>>::type;
};
template <class T, class Y>
using array_type_swap_t = typename array_type_swap<T, Y>::type;
template <class>
inline constexpr bool is_std_array_v = false;
template <class T, std::size_t N>
inline constexpr bool is_std_array_v<std::array<T, N>> = true;
// Get element count of a std::array, or C style array as constexpr. The value
// is 1 for all other types (as in array of 1).
template <class T>
struct contexpr_array_size {
static std::size_t constexpr size = 1;
};
template <class T, std::size_t N>
struct contexpr_array_size<T[N]> {
static std::size_t constexpr size = N;
};
template <class T, std::size_t N>
struct contexpr_array_size<std::array<T, N>> {
static std::size_t constexpr size = N;
};
template <class T>
inline auto constexpr contexpr_array_size_v = contexpr_array_size<T>::size;
template <class T, class Y>
void copy_array(T& dst, Y const& src) {
static_assert(contexpr_array_size_v<T> == contexpr_array_size_v<Y>,
"Can only copy arrays of the same size");
if constexpr (!is_std_array_v<Y> && !std::is_array_v<Y>)
dst = static_cast<T>(src);
else
for (std::size_t i = 0; i < contexpr_array_size_v<Y>; ++i)
copy_array(dst[i], src[i]);
}
template <class T, class Y>
auto uniform(Y const& arr) {
array_type_swap_t<T, Y> result;
copy_array(result, arr);
return result;
}
int main() {
std::puts("built-in array :");
const int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
auto b = uniform<float>(a);
for (auto& row : b) {
for (auto& elm : row) std::printf("%.2f ", elm);
std::putchar('\n');
}
std::puts("\nstd::array :");
std::array<std::array<int, 3>, 2> stdarr = {{{6, 5, 4}, {3, 2, 1}}};
b = uniform<float>(stdarr);
for (auto& row : b) {
for (auto& elm : row) std::printf("%.2f ", elm);
std::putchar('\n');
}
}
You can't return [] arrays from a function, so the 1D version doesn't work.
You can use std::array.
template <class D, class T, std::size_t S1, std::size_t S2>
inline std::array<std::array<D, S1>, S2> uniform(std::array<std::array<T, S1>, S2> t) {
std::array<std::array<D, S1>, S2> v;
std::array<D, S1>(*inner)(std::array<T, S1>) = uniform<D, T, S1>;
std::transform(t.begin(), t.end(), v.begin(), inner);
return v;
}
template <class D, class T, std::size_t S>
inline std::array<D, S> uniform(std::array<T, S> t) {
return { t.begin(), t.end() };
}
I would like to write a C++ function that can count the total number of "atomic" elements in a generic nested STL-like container, with the following conditions:
Each level may be any type of container.
The number of levels is not given a priori.
I wrote the following recursive function (using code from here):
template <typename T>
size_t get_total_size(const T & var)
{
if ( is_container<typeof(var)>::value ) { // From https://stackoverflow.com/a/9407521/2707864
typename T::const_iterator iter;
size_t sumsize = 0;
for ( iter = var.begin() ; iter!= var.end() ; iter++ ) {
sumsize += get_total_size(*iter);
}
return sumsize;
} else {
return 1;
}
};
Compiling/linking this may work.
The problem is that upon using it (otherwise, there wouldn't be any point in writing it!) does not compile, as the instantiation gets at the "atomic" level to a type that does not have iterators, e.g., in this code
typedef vector<int> vint;
typedef vector<vint> vvint;
vvint vec_heap;
for (int i=0; i < 12; i++) {
vec_heap.push_back(vint(2, i));
}
cout << get_total_size(vec_heap) << endl; // Instantiation leads to compilation errors
Is this possible?
EDIT:
As per one comment, it can be done with c++17...
Is the recursive function I wrote an overkill for the objective?
With C++17, you might use if constexpr:
template <typename T>
size_t get_total_size(const T& var)
{
if constexpr (is_container<T>::value) {
return std::accumulate(var.begin(),
var.end(),
0u,
[](int acc, const auto& e){ return acc + get_total_size(e); });
} else {
return 1u;
}
};
Before that, you might use overloads and SFINAE:
// this will be called when T is not a container (it is the "atomic" type)
template <typename T, std::enable_if_t<!is_container<T>::value, int> = 0>
size_t get_total_size(const T& var)
{
return 1u;
};
// this will be called for all container types, except for maps
template <typename T, std::enable_if_t<is_container<T>::value, int> = 0>
size_t get_total_size(const T& var)
{
return std::accumulate(var.begin(),
var.end(),
0u,
[](int acc, const auto& e){ return acc + get_total_size(e); });
};
// this will be called for maps
template <typename Key, typename T>
size_t get_total_size(const std::map<Key, T> & var)
{
return std::accumulate(var.begin(),
var.end(),
0u,
[](int acc, const auto& e){ return acc + get_total_size_sfinae(e.second); });
}
If you can't use C++17, or just want to open up what standard can be used with your function then you can switch to using two overloads and use SFINAE to determine when to call each overload. Using
// this will be called when T is not a container (it is the "atomic" type)
template <typename T, typename std::enable_if<!is_container<T>::value, bool>::type = true>
size_t get_total_size(const T & var)
{
return 1;
}
// forward declare of pair function for associative containers
template <typename T, typename U>
size_t get_total_size(const std::pair<T, U> & var);
// this will be called for all container types
template <typename T, typename std::enable_if<is_container<T>::value, bool>::type = true>
size_t get_total_size(const T & var)
{
size_t sumsize = 0;
for ( auto iter = var.begin() ; iter != var.end() ; ++iter ) {
sumsize += get_total_size(*iter);
}
return sumsize;
}
// this will be called for pair to handle associative containers
template <typename T, typename U>
size_t get_total_size(const std::pair<T, U> & var)
{
return get_total_size(var.first) + get_total_size(var.second);
}
This will work from C++11 on up which you can see in this live example
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 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;
}
This is a continuation of my question Implement STL functions in variadic template:
How can I implement a subscript operator for an N-dimensional array that accepts an array of length N, given that a already have defined "single-layer" subscript operators to be used as array[indx0][indx1]…[indxN]. I feel like there should be a simple fold expression for this?
So, [] is not one of the foldable operators. Bummer. But we just have to cheat a bit and piggyback on another one :)
namespace indexer_detail {
template <class T>
struct ArrayWrapper {
T obj;
};
template <class T>
ArrayWrapper(T&&) -> ArrayWrapper<T&&>;
template <class T>
auto operator & (ArrayWrapper<T> const &aw, std::size_t N) {
return ArrayWrapper{aw.obj[N]};
}
}
template <std::size_t Size, class Array, std::size_t... Idx>
decltype(auto) index(
Array &&array,
std::array<std::size_t, Size> const &indices,
std::index_sequence<Idx...>
) {
return (
indexer_detail::ArrayWrapper{std::forward<Array>(array)} & ... & indices[Idx]
).obj;
}
template <std::size_t Size, class Array>
decltype(auto) index(Array &&array, std::array<std::size_t, Size> const &indices) {
return index(std::forward<Array>(array), indices, std::make_index_sequence<Size>{});
}
See it live on Coliru
Because it's C++17, we can use constexpr if to make more it simple :
template<size_t Idx0, size_t... IdxRest, typename V>
decltype(auto) getval(const V& v)
{
if constexpr (sizeof...(IdxRest) == 0)
{
return v[Idx0];
}
else
{
return getval<IdxRest...>(v[Idx0]);
}
}
int main()
{
int arr[10][10] = { 1,2,3 };
std::vector<int> arr2 = { 1,2,3 };
auto v1 = getval<0, 2>(arr);
auto v2 = getval<1>(arr2);
return 0;
}