I have this code. This is an array (which is a bit like std :: array) that I could work with at compile time. Also for-loop at compile time.
#include <utility>
#include <memory>
#include <type_traits>
template<class F,
std::size_t ... Is>
constexpr void __loop(F&& func, std::index_sequence<Is ...>) noexcept
{
(func(std::integral_constant<std::size_t, Is>{}), ...);
}
template<std::size_t N,
typename F>
constexpr void CONSTEXPR_LOOP(F&& func) noexcept
{
__loop(std::forward<F>(func), std::make_index_sequence<N>());
}
template<typename T, std::size_t Size>
class StaticArray
{
static_assert(std::disjunction_v<
std::is_default_constructible<T>,
std::is_nothrow_default_constructible<T>
>,
"Type must have a trivial constructor.");
public:
constexpr StaticArray() noexcept;
template<typename ... Args,
std::enable_if_t<
std::conjunction_v<
std::is_same<T, Args>...
>
> * = nullptr
>
constexpr StaticArray(Args && ... list) noexcept;
constexpr StaticArray(const StaticArray& a) = delete;
constexpr StaticArray(StaticArray&& a) = delete;
~StaticArray() noexcept = default;
constexpr StaticArray& operator=(const StaticArray& a) = delete;
constexpr StaticArray& operator=(StaticArray&& a) = delete;
constexpr const T& operator[](std::size_t i) const noexcept;
private:
T _data[Size];
std::size_t _capacity;
std::size_t _count;
template<typename Arg>
constexpr void set_data(std::size_t i, Arg&& arg) noexcept;
template<typename ... Args, std::size_t ... Indices>
constexpr void unpack(std::index_sequence<Indices ...>, Args&& ... args) noexcept;
template<typename ... Args>
constexpr void create_indexes(Args&& ... args) noexcept;
};
template<typename T, std::size_t Size>
constexpr StaticArray<T, Size>::StaticArray() noexcept :
_data{T{}},
_capacity{Size},
_count{0}
{
}
template<typename T, std::size_t Size>
template<typename ... Args,
std::enable_if_t<
std::conjunction_v<
std::is_same<T, Args>...
>
> *
>
constexpr StaticArray<T, Size>::StaticArray(Args&& ... list) noexcept :
_data{T{}},
_capacity{Size},
_count{Size}
{
static_assert(Size == sizeof ... (list), "Size of array not equal number of elements in the list");
static_assert(std::conjunction_v<std::is_same<T, Args>... >, "Parameter must be the same type as StaticArray<T>.");
create_indexes(std::forward<Args>(list) ...);
}
template<typename T, std::size_t Size>
template<typename Arg>
constexpr void StaticArray<T, Size>::set_data(std::size_t i, Arg&& arg) noexcept
{
_data[i] = arg;
}
template<typename T, std::size_t Size>
template<typename ... Args, std::size_t ... Indices>
constexpr void StaticArray<T, Size>::unpack(std::index_sequence<Indices ...>, Args&& ... args) noexcept
{
(set_data(Indices, args), ...);
}
template<typename T, std::size_t Size>
template<typename ... Args>
constexpr void StaticArray<T, Size>::create_indexes(Args&& ... args) noexcept
{
unpack(std::make_index_sequence<Size>{}, std::forward<Args>(args)...);
}
template<typename T, std::size_t Size>
constexpr const T& StaticArray<T, Size>::operator[](std::size_t i) const noexcept
{
return _data[i];
}
int main()
{
constexpr StaticArray<unsigned, 10> array = {9u, 8u, 7u, 6u, 5u, 4u, 3u, 2u, 1u, 0u};
static_assert(array[0] == 9);
static_assert(array[1] == 8);
static_assert(array[2] == 7);
static_assert(array[3] == 6);
static_assert(array[4] == 5);
static_assert(array[5] == 4);
static_assert(array[6] == 3);
static_assert(array[7] == 2);
static_assert(array[8] == 1);
static_assert(array[9] == 0);
constexpr std::array<unsigned, 10> checker = {9u, 8u, 7u, 6u, 5u, 4u, 3u, 2u, 1u, 0u};
CONSTEXPR_LOOP<10>([&](auto i) constexpr {
static_assert(array[i] == checker[i]);
});
return 0;
}
And when I compile this using g++-8.3, I get this error:
.../main.cpp: In instantiation of ‘main()::<lambda(auto:1)> [with auto:1 = std::integral_constant<long unsigned int, 0>]’:
.../main.cpp:9:10: required from ‘constexpr void __loop(F&&, std::index_sequence<Is ...>) [with F = main()::<lambda(auto:1)>; long unsigned int ...Is = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; std::index_sequence<Is ...> = std::integer_sequence<long unsigned int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>]’
.../main.cpp:16:11: required from ‘constexpr void CONSTEXPR_LOOP(F&&) [with long unsigned int N = 10; F = main()::<lambda(auto:1)>]’
.../main.cpp:149:6: required from here
.../main.cpp:148:32: error: non-constant condition for static assertion
static_assert(array[i] == checker[i]);
~~~~~~~~~^~~~~~~~~~~
.../main.cpp:148:32: error: ‘__closure’ is not a constant expression
After I spent some time to understand what the problem was, I decided to compile this code using g++-7.4. It compiles successfully without any errors. Clang-6 and g++-9 give me the same result, but as soon as I use g++-8, I get the errors described above. Any idea why this is happening?
Thanks!
[Note] Online example: https://godbolt.org/z/Ig4CCW
[UPDATE] I compiled this code in g++-8 when I added a static specifier to the constexpr variable. It works because:
enter link description here
A lambda expression can use a variable without capturing it if the variable
is a non-local variable or has static or thread local storage duration >(in which case the variable cannot be captured)
But if you look at the code below, you will notice that the lambda, which is called from another function, for some reason does not capture the constexpr variable by reference and value in g++-8. Other compilers do not report any errors.
template<typename F>
constexpr void call(F&& f)
{
f();
}
int main()
{
constexpr std::array<unsigned, 1> checker = {1u};
call([&]() constexpr { static_assert(checker[0] == checker[0]); });
static constexpr std::array<unsigned, 1> checker2 = {1u};
call([]() constexpr { static_assert(checker2[0] == checker2[0]); });
constexpr std::array<unsigned, 1> checker3 = {1u};
call([=]() constexpr { static_assert(checker3[0] == checker3[0]); });
return 0;
}
Try it
AFAIK parameter, even in a constexpr function aren't constexpr
constexpr void f(std::size_t n) {
static_assert(n == 42, ""); // not allowed.
}
Source : https://mpark.github.io/programming/2017/05/26/constexpr-function-parameters/
UPDATE : from comments
I got fooled by the auto. Indeed, since the call is here :
func(std::integral_constant<std::size_t, Is>{}), ...);
auto is an std::integral_constant and it should work
Well, I think this is probably a bug in g++8. Lambda does not capture the constexpr variable, and the code below very clearly demonstrates this:
template<typename F>
constexpr void call(F&& f)
{
f();
}
int main()
{
constexpr std::array<unsigned, 1> checker = {1u};
call([&]() constexpr { static_assert(checker[0] == checker[0]); }); // compile error
static constexpr std::array<unsigned, 1> checker2 = {1u};
call([]() constexpr { static_assert(checker2[0] == checker2[0]); }); // OK!
constexpr std::array<unsigned, 1> checker3 = {1u};
call([=]() constexpr { static_assert(checker3[0] == checker3[0]); }); // compile error
return 0;
}
I did not find any mention of this problem, so I really think that this is a bug in g++8.
Also, I found three solutions to avoid this error. If you get the same error, you must do one of the three:
Mark your variable as static. Lambda can use a static variable without capturing.
Wrap your constexpr variable in structure using templates:
template<std::size_t Size, typename T, T ... ARGS>
struct ArrayWrapper
{
static constexpr std::array<T, Size> value = {ARGS ...};
};
constexpr ArrayWrapper<10, unsigned,
9u, 8u, 7u, 6u, 5u, 4u, 3u, 2u, 1u, 0u> wrapper;
Use another compiler. g++-7, g++-9 and clang compile this without any errors.
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 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 have the following code where I am irritated by the fact that compiler is unable to see that variable passed as argument to a function is constexpr so I must use arity 0 function instead of 1 argument function.
I know this is not a compiler bug, but I wonder if there are idioms that enable to workaround this problem.
#include <array>
#include <iostream>
static constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};
template <typename C, typename P, typename Y>
static constexpr void copy_if(const C& rng, P p, Y yi3ld) {
for (const auto& elem: rng) {
if (p(elem)){
yi3ld(elem);
}
}
}
// template<std::size_t N>
static constexpr auto get_evens(/* const std::array<int, N>& arr */) {
constexpr auto is_even = [](const int i) constexpr {return i % 2 == 0;};
constexpr int cnt = [/* &arr, */&is_even]() constexpr {
int cnt = 0;
auto increment = [&cnt] (const auto&){cnt++;};
copy_if(arr, is_even, increment);
return cnt;
}();
std::array<int, cnt> result{};
int idx = 0;
copy_if(arr, is_even, [&result, &idx](const auto& val){ result[idx++] = val;});
return result;
}
int main() {
// constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};
for (const int i:get_evens(/* arr */)) {
std::cout << i << " " << std::endl;
}
}
If it is not obvious what I want: I would like to change get_evens signature so that it is template templated on array size N and that it takes 1 argument of type const std::array<int, N>&.
The error message when I change arr to be an function argument isn't helpful:
prog.cc:25:21: note: initializer of 'cnt' is not a constant expression
prog.cc:19:19: note: declared here
constexpr int cnt = [&arr, &is_even]()constexpr {
A function argument is never a constant expression, even if a function is used in constexpr context:
constexpr int foo(int i)
{
// i is not a constexpr
return i + 1;
}
constexpr auto i = 1;
constexpr auto j = foo(i);
To mimic a constexpr argument, use a template parameter:
template<int i>
constexpr int foo()
{
// i is constexpr
return i + 1;
}
constexpr auto i = 1;
constexpr auto j = foo<i>();
A possible solution is to use std::integer_sequence to encode integers into a type:
#include <array>
#include <iostream>
#include <type_traits>
template<typename P, typename Y, int... elements>
constexpr void copy_if_impl(P p, Y yi3ld, std::integer_sequence<int, elements...>) {
((p(elements) && (yi3ld(elements), true)), ...);
}
template<typename arr_t, typename P, typename Y>
constexpr void copy_if(P p, Y yi3ld) {
copy_if_impl(p, yi3ld, arr_t{});
}
template<typename arr_t>
constexpr auto get_evens(){
constexpr auto is_even = [](const int i) constexpr { return i % 2 == 0; };
constexpr int cnt = [&is_even]() constexpr {
int cnt = 0;
auto increment = [&cnt](const auto&) { cnt++; };
copy_if<arr_t>(is_even, increment);
return cnt;
}();
std::array<int, cnt> result{};
int idx = 0;
copy_if<arr_t>(is_even, [&result, &idx](const auto& val) {
result[idx++] = val; });
return result;
}
int main()
{
using arr = std::integer_sequence<int, 11, 22, 33, 44, 55>;
for (const int i : get_evens<arr>()) {
std::cout << i << " " << std::endl;
}
}
Addition suggested by Constantinos Glynos.
From Effective Modern C++ book by Scott Meyers, item 15, p.98:
constexpr functions can be used in contexts that demand compile-time constants. If the values of the arguments you pass to a constexpr function in such a context are known during compilation, the result will be computed during compilation. If any of the arguments’ values is not known during compilation, your code will be rejected.
When a constexpr function is called with one or more values that are not known during compilation, it acts like a normal function, computing its result at runtime. This means you don’t need two functions to perform the same operation, one for compile-time constants and one for all other values. The constexpr function does it all.
The other answer has a correct work around but I think the reasoning has nothing to do with parameters but instead to do with the lambda capture here:
constexpr int cnt = [/* &arr, */&is_even]()
Indeed we can test the various scenarios with this code:
#include <array>
#include <iostream>
template <size_t N>
constexpr int foo(const std::array<int, N>& arr) {
return [&arr] () { return arr.size(); }();
}
template <size_t N>
constexpr int bar(const std::array<int, N>& arr) {
int res{};
for (auto i : arr) {
res++;
}
return res;
}
template <size_t N>
constexpr int baz(const std::array<int, N>& arr) {
constexpr int test = [&arr] () constexpr {
return bar(arr);
}();
return test;
}
int main() {
constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};
constexpr std::array<int, foo(arr)> test{};
constexpr std::array<int, bar(arr)> test2{};
constexpr std::array<int, baz(arr)> test3{};
}
Note that the line where test3 is initialized fails to compile. This, however, compiles just fine:
template <size_t N>
constexpr int baz(const std::array<int, N>& arr) {
return bar(arr);
}
So, what's the problem here? Well lambdas are really just glorified functors, and internally it'll look something like this:
struct constexpr_functor {
const std::array<int, 5>& arr;
constexpr constexpr_functor(const std::array<int, 5>& test)
: arr(test) { }
constexpr int operator()() const {
return bar(arr);
}
};
// ...
constexpr constexpr_functor t{arr};
constexpr std::array<int, t()> test3{};
Notice now that we get an error message showing the real problem:
test.cpp:36:33: note: reference to 'arr' is not a constant expression
test.cpp:33:34: note: declared here
constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};
The other answer quotes Scotts Meyer's book but misinterprets the quotes. The book actually shows several examples of parameters being used in constexpr situations, but the quotes are simply saying that if you pass a non-constexpr parameter, the function can run at compile-time.
Following the Evg's suggestion, so passing the numbers as template parameters of a std::integer_sequence, but passing the integer sequence as argument of the get_evens() function, and not as template parameter, you can use the numbers directly inside get_evens().
I mean... you can simplify the get_evens() as follows (EDIT: further simplified following a suggestion from Evg (Thanks!))
template <typename T, T ... Ts>
constexpr auto get_evens (std::integer_sequence<T, Ts...> const &)
{
std::array<T, (std::size_t(!(Ts & T{1})) + ...)> result{};
std::size_t idx = 0;
((void)(Ts & 1 || (result[idx++] = Ts, true)), ...);
return result;
}
and you can use it this way
int main()
{
using arr = std::integer_sequence<int, 11, 22, 33, 44, 55>;
for ( const int i : get_evens(arr{}) )
std::cout << i << " " << std::endl;
}
#include <array>
#include <iostream>
static constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};
template <typename C, typename P, typename T>
static constexpr void invoke_if(const C& rng, P p, T target) {
for (const auto& elem: rng) {
if (p(elem)){
target(elem);
}
}
}
constexpr bool is_even(int i) {
return i % 2 == 0;
}
template<std::size_t N>
constexpr std::size_t count_evens(const std::array<int, N>& arr)
{
std::size_t cnt = 0;
invoke_if(arr, is_even, [&cnt](auto&&){++cnt;});
return cnt;
}
template<std::size_t cnt, std::size_t N>
static constexpr auto get_evens(const std::array<int, N>& arr) {
std::array<int, cnt> result{};
int idx = 0;
invoke_if(arr, is_even, [&result, &idx](const auto& val){ result[idx++] = val;});
return result;
}
int main() {
// constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};
for (const int i:get_evens<count_evens(arr)>(arr)) {
std::cout << i << " " << std::endl;
}
}
this works in g++, but in clang we get a problem because the begin on an array isn't properly constexpr with at least one library. Or maybe g++ violates the standard and clang does not.
template<auto t0, auto...ts>
struct ct_array:
std::array<decltype(t0) const, 1+sizeof...(ts)>,
std::integer_sequence<decltype(t0), t0, ts...>
{
ct_array():std::array<decltype(t0) const, 1+sizeof...(ts)>{{t0, ts...}} {};
};
template<class target, auto X>
struct push;
template<auto X>
struct push<void, X>{using type=ct_array<X>;};
template<auto...elems, auto X>
struct push<ct_array<elems...>, X>{using type=ct_array<elems...,X>;};
template<class target, auto X>
using push_t= typename push<target, X>::type;
template<class target>
struct pop;
template<auto x>
struct pop<ct_array<x>>{using type=void;};
template<auto x0, auto...xs>
struct pop<ct_array<x0, xs...>>{using type=ct_array<xs...>;};
template<class target>
using pop_t=typename pop<target>::type;
template<class lhs, class rhs, class F, class=void>
struct transcribe;
template<class lhs, class rhs, class F>
using transcribe_t = typename transcribe<lhs, rhs, F>::type;
template<auto l0, auto...lhs, class rhs, class F>
struct transcribe<ct_array<l0, lhs...>, rhs, F,
std::enable_if_t<F{}(l0) && sizeof...(lhs)>
>:
transcribe<pop_t<ct_array<l0, lhs...>>, push_t<rhs, l0>, F>
{};
template<auto l0, auto...lhs, class rhs, class F>
struct transcribe<ct_array<l0, lhs...>, rhs, F,
std::enable_if_t<!F{}(l0) && sizeof...(lhs)>
>:
transcribe<pop_t<ct_array<l0, lhs...>>, rhs, F>
{};
template<auto lhs, class rhs, class F>
struct transcribe<ct_array<lhs>, rhs, F, void>
{
using type=std::conditional_t< F{}(lhs), push_t<rhs, lhs>, rhs >;
};
template<class lhs, class F>
using filter_t = transcribe_t<lhs, void, F>;
// C++20
//auto is_even = [](auto i)->bool{ return !(i%2); };
struct is_even_t {
template<class T>
constexpr bool operator()(T i)const{ return !(i%2); }
};
constexpr is_even_t is_even{};
template<auto...is>
static constexpr auto get_evens(ct_array<is...>) {
return filter_t< ct_array<is...>, decltype(is_even) >{};
}
Live example.
Test code:
auto arr = ct_array<11, 22, 33, 44, 55>{};
for (const int i : get_evens(arr)) {
std::cout << i << " " << std::endl;
}
The title is bad but I couldn't come up with anything better. Feel free to change it.
Here's a template multidimensional array class that I'm currently working on. I'm trying to optimise it as much as I can:
#include <array>
template <typename T, std::size_t... Dimensions>
class multidimensional_array
{
public:
using value_type = T;
using size_type = std::size_t;
private:
template<typename = void>
static constexpr size_type multiply(void)
{
return 1u;
}
template<std::size_t First, std::size_t... Other>
static constexpr size_type multiply(void)
{
return First * multidimensional_array::multiply<Other...>();
}
public:
using container_type = std::array<value_type, multidimensional_array::multiply<Dimensions...>()>;
using reference = value_type &;
using const_reference = value_type const&;
using iterator = typename container_type::iterator;
private:
container_type m_data_array;
template<typename = void>
static constexpr size_type linearise(void)
{
return 0u;
}
template<std::size_t First, std::size_t... Other>
static constexpr size_type linearise(std::size_t index, std::size_t indexes...)
{
return multidimensional_array::multiply<Other...>()*index + multidimensional_array::linearise<Other...>(indexes);
}
public:
// Constructor
explicit multidimensional_array(const_reference value = value_type {})
{
multidimensional_array::fill(value);
}
// Accessors
reference operator()(std::size_t indexes...)
{
return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
}
const_reference operator()(std::size_t indexes...) const
{
return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
}
// Iterators
iterator begin()
{
return m_data_array.begin();
}
iterator end()
{
return m_data_array.end();
}
// Other
void fill(const_reference value)
{
m_data_array.fill(value);
}
};
My main function is
int main(void)
{
multidimensional_array<int, 2u, 3u, 4u, 5u, 6u> foo;
int k = 0;
for (auto& s : foo)
s = k++;
//std::cout << foo(0u, 0u, 0u, 1u, 0u) << std::endl;
return 0;
}
The above code compilers without warning/error. As soon as I uncomment the std::cout part though, I get this:
g++-7 -std=c++17 -o foo.o -c foo.cpp -Wall -Wextra -pedantic
foo.cpp: In instantiation of ‘multidimensional_array<T, Dimensions>::value_type& multidimensional_array<T, Dimensions>::operator()(std::size_t, ...) [with T = int; long unsigned int ...Dimensions = {2, 3, 4, 5, 6}; multidimensional_array<T, Dimensions>::reference = int&; multidimensional_array<T, Dimensions>::value_type = int; std::size_t = long unsigned int]’:
foo.cpp:99:37: required from here
foo.cpp:60:72: error: no matching function for call to ‘multidimensional_array<int, 2, 3, 4, 5, 6>::linearise<2, 3, 4, 5, 6>(std::size_t&)’
return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
foo.cpp:38:30: note: candidate: template<class> static constexpr multidimensional_array<T, Dimensions>::size_type multidimensional_array<T, Dimensions>::linearise() [with <template-parameter-2-1> = <template-parameter-1-1>; T = int; long unsigned int ...Dimensions = {2, 3, 4, 5, 6}]
static constexpr size_type linearise(void)
^~~~~~~~~
foo.cpp:38:30: note: template argument deduction/substitution failed:
foo.cpp:60:72: error: wrong number of template arguments (5, should be at least 0)
return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
foo.cpp:44:30: note: candidate: template<long unsigned int First, long unsigned int ...Other> static constexpr multidimensional_array<T, Dimensions>::size_type multidimensional_array<T, Dimensions>::linearise(std::size_t, std::size_t, ...) [with long unsigned int First = First; long unsigned int ...Other = {Other ...}; T = int; long unsigned int ...Dimensions = {2, 3, 4, 5, 6}]
static constexpr size_type linearise(std::size_t index, std::size_t indexes...)
^~~~~~~~~
foo.cpp:44:30: note: template argument deduction/substitution failed:
foo.cpp:60:72: note: candidate expects 2 arguments, 1 provided
return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
Makefile:17: recipe for target 'foo.o' failed
make: *** [foo.o] Error 1
And I know why now. My question is, how can I fix linearise so that it can pass indexes without going through va_list and such? Unfortunately linearise is already a template, variadic function, so I can't use variadic template shenanigans in that regard.
As in preceding question the problem is that the following signatures
template<std::size_t First, std::size_t... Other>
static constexpr size_type linearise(std::size_t index,
std::size_t indexes...)
reference operator()(std::size_t indexes...)
const_reference operator()(std::size_t indexes...) const
aren't what do you mean (indexes a variadic list of std::size_t) but are exactly equivalent to
template<std::size_t First, std::size_t... Other>
static constexpr size_type linearise(std::size_t index,
std::size_t indexes,
...)
reference operator()(std::size_t indexes, ...)
const_reference operator()(std::size_t indexes, ...) const
where indexes is a single std::size_t followed by a C-style optional sequence of argument.
A simple solution (you tagged C++17 but is available starting from C++11) is based on the use of variadic templates.
By example, as follows
template <std::size_t First, std::size_t ... Other, typename ... Ts>
static constexpr size_type linearise (std::size_t index,
Ts ... indexes)
{ return multidimensional_array::multiply<Other...>() * index
+ multidimensional_array::linearise<Other...>(indexes...); }
// Accessors
template <typename ... Ts>
reference operator() (Ts ... indexes)
{ return m_data_array[
multidimensional_array::linearise<Dimensions...>(indexes...)]; }
template <typename ... Ts>
const_reference operator() (Ts ... indexes) const
{ return m_data_array[
multidimensional_array::linearise<Dimensions...>(indexes...)]; }
The following is you're code, modified and compilable
#include <array>
#include <iostream>
template <typename T, std::size_t ... Dimensions>
class multidimensional_array
{
public:
using value_type = T;
using size_type = std::size_t;
private:
template <typename = void>
static constexpr size_type multiply ()
{ return 1u; }
template <std::size_t First, std::size_t ... Other>
static constexpr size_type multiply(void)
{ return First * multidimensional_array::multiply<Other...>(); }
public:
using container_type = std::array<value_type,
multidimensional_array::multiply<Dimensions...>()>;
using reference = value_type &;
using const_reference = value_type const &;
using iterator = typename container_type::iterator;
private:
container_type m_data_array;
template <typename = void>
static constexpr size_type linearise ()
{ return 0u; }
template <std::size_t First, std::size_t ... Other, typename ... Ts>
static constexpr size_type linearise (std::size_t index,
Ts ... indexes)
{ return multidimensional_array::multiply<Other...>() * index
+ multidimensional_array::linearise<Other...>(indexes...); }
public:
// Constructor
explicit multidimensional_array (const_reference value = value_type{})
{ multidimensional_array::fill(value); }
// Accessors
template <typename ... Ts>
reference operator() (Ts ... indexes)
{ return m_data_array[
multidimensional_array::linearise<Dimensions...>(indexes...)]; }
template <typename ... Ts>
const_reference operator() (Ts ... indexes) const
{ return m_data_array[
multidimensional_array::linearise<Dimensions...>(indexes...)]; }
// Iterators
iterator begin ()
{ return m_data_array.begin(); }
iterator end ()
{ return m_data_array.end(); }
// Other
void fill (const_reference value)
{ m_data_array.fill(value); }
};
int main ()
{
multidimensional_array<int, 2u, 3u, 4u, 5u, 6u> foo;
int k{ 0 };
for ( auto & s : foo )
s = k++;
std::cout << foo(0u, 0u, 0u, 1u, 0u) << std::endl;
}
Bonus suggestion.
You tagged C++17 so you can use "folding".
So you can substitute the couple of multiply() template functions
template <typename = void>
static constexpr size_type multiply ()
{ return 1u; }
template <std::size_t First, std::size_t ... Other>
static constexpr size_type multiply ()
{ return First * multidimensional_array::multiply<Other...>(); }
with a single folded one
template <std::size_t ... Sizes>
static constexpr size_type multiply ()
{ return ( 1U * ... * Sizes ); }
My approach is similar to that in this answer, except that instead of using std::tuple to store a list of types, I define my own type size_t_pack to store a (compile-time) list of size_t's.
using std::size_t;
template<size_t... values>
struct size_t_pack{};
template<size_t first_value,size_t... rest_values>
struct size_t_pack<first_value,rest_values...>{
static constexpr size_t first=first_value;
using rest=size_t_pack<rest_values...>;
static constexpr size_t product=first*rest::product;
};
template<>struct size_t_pack<>{
static constexpr size_t product=1;
};
Defines members: first, rest (in case not empty) and product (since it's not possible to specialize a function using the templates of a template argument, as far as I know, another choice is to if constexpr and make the type support checking for empty)
With that, it's easy to define the linearize function:
template<class dimensions,class... SizeTs>
static constexpr size_type linearise(std::size_t index, SizeTs... indices)
{
using restDimensions=typename dimensions::rest;
return restDimensions::product *index +
multidimensional_array::linearise<restDimensions>(indices...);
}
Using a std::tuple to store the list of types (SizeTs) is also possible, although struct partial specialization is still required, as far as I know.
You need to make indexes a parameter pack by making the operator() function a template, and expand the parameter pack when you use it by putting ... afterwards:
template <class... DimensionType>
const_reference operator()(DimensionType... indexes) const
{
return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes...)];
}
See: parameter pack expansion
The code still will not compile because of a similar problem in linearize(), but that gets you on the right track.
I had nested partially specialized template code working with VS 2015 until I discovered that it was not standards-compliant. I want it to be so I twisted my code to overcome the former issue and also that one and have now hit a hard wall.
Using variadic templates and partial specialization I would like to fill an array at compile-time given a fixed set of parameters.
What I want to achieve also seems similar to this answer but I did not manage to make it work.
Consider the following program:
#include <cstdlib>
template <typename T, std::size_t Size>
struct Array;
template <typename T, std::size_t Size, std::size_t Iteration, typename ...Args>
struct ArrayFiller {
inline
static void fill(Array<T, Size>& a, const Args&... args) {
ArrayFiller<T, Size, Iteration, Args...>::fill_recursive(a, args...);
}
inline
static void fill_recursive(Array<T, Size>& a, const T& i, const Args&... args) {
a.data[Size - Iteration - 1] = i;
ArrayFiller<T, Size, Iteration - 1>::fill_recursive(a, args...);
}
};
template <typename T, std::size_t Size>
struct ArrayFiller<T, Size, 0> {
inline
static void fill_recursive(Array<T, Size>& a, const T& i) {
a.data[Size - 1] = i;
}
};
template <typename T, std::size_t Size>
struct Array {
T data[Size];
template <typename ...Args>
Array(const Args&... args) {
ArrayFiller<T, Size, Size - 1, Args...>::fill(*this, args...);
}
};
int main() {
Array<int, 2> c(42, -18);
return 0;
}
...and the beginning of its g++ -std=c++14 -pedantic -Wall -Wextra output (as of version 5.3.0):
main.cpp: In instantiation of ‘static void ArrayFiller<T, Size, Iteration, Args>::fill(Array<T, Size>&, const Args& ...) [with T = int; long unsigned int Size = 2ul; long unsigned int Iteration = 1ul; Args = {int, int}]’:
main.cpp:34:54: required from ‘Array<T, Size>::Array(const Args& ...) [with Args = {int, int}; T = int; long unsigned int Size = 2ul]’
main.cpp:39:28: required from here
main.cpp:10:65: error: no matching function for call to ‘ArrayFiller<int, 2ul, 1ul, int, int>::fill_recursive(Array<int, 2ul>&, const int&, const int&)’
ArrayFiller<T, Size, Iteration, Args...>::fill_recursive(a, args...);
^
main.cpp:14:17: note: candidate: static void ArrayFiller<T, Size, Iteration, Args>::fill_recursive(Array<T, Size>&, const T&, const Args& ...) [with T = int; long unsigned int Size = 2ul; long unsigned int Iteration = 1ul; Args = {int, int}]
static void fill_recursive(Array<T, Size>& a, const T& i, const Args&... args) {
^
main.cpp:14:17: note: candidate expects 4 arguments, 3 provided
Basically the compiler complains that there is no matching function because from what I understand the parameter pack is expanded either too "soon" or too "late" in my logic: the const T& i argument in the recursive call messes up the expansion.
How would you fix it?
I am also interested in alternate / better / cleaner solutions.
Is a solution not based on template recursion acceptable in your use case? wandbox link
template <typename T, std::size_t Size>
struct Array {
T data[Size];
template <typename ...Args>
constexpr Array(const Args&... args) : data{args...} {
}
};
int main() {
Array<int, 2> c(42, -18);
assert(c.data[0] == 42);
assert(c.data[1] == -18);
constexpr Array<int, 2> cc(42, -18);
static_assert(cc.data[0] == 42);
static_assert(cc.data[1] == -18);
}
I might be off target here but based on this requirement "... I would like to fill an array at compile-time given a fixed set of parameters." and this code:
int main() {
Array<int, 2> c(42, -18);
return 0;
}
I have been left wondering is this not solvable with a normal array declaration and initialization?
int main() {
constexpr int c []{42, -18};
static_assert( c[0] == 42 ) ;
// and so on
return 0;
}
In a comment to the previous answer, you are mentioning some setter? There must be something missing in here... In case you need to have this class Array as above, perhaps the simplest way to do so is this:
template<typename T, T ... V >
struct Array
{
constexpr static T data_[]{ V... };
// not strictly necessary
constexpr static size_t size{ sizeof(data_) / sizeof(T) };
};
Usage is this:
// length is not required for declaration
using int_array_of_4 = Array<int,1,2,3,4> ;
static_assert( int_array_of_4::data_[0] == 1) ;
// and so on
But I might be barking on the wrong tree here?