Related
I have a wrapper struct around a vector and a shape, like this:
template <std::size_t N>
struct array_type {
std::array<std::size_t, N> shape;
std::vector<float> data;
};
I would like to be able to construct an array_type from any array float[N], float[N][M], etc.
Currently, I have a function for each rank, e.g.
template <std::size_t N>
array_type<1> _1d(const deel::formal::float_type (&values)[N]) {
return {{N},
std::vector<float_type>(
reinterpret_cast<const float_type*>(values),
reinterpret_cast<const float_type*>(values) + N)};
}
template <std::size_t N>
array_type<2> _2d(const deel::formal::float_type (&values)[N][M]) {
return {{N, M},
std::vector<float_type>(
reinterpret_cast<const float_type*>(values),
reinterpret_cast<const float_type*>(values) + N * M)};
}
I would like to write something like:
template <class Array>
array_type<std::rank_v<Array>> make_array(Array const&);
...but that does not work for initializer list:
auto arr1 = _1d({1, 2, 3}); // Ok
auto arr2 = make_array({1, 2, 3}); // Ko
Is there a way to have this make_array? Or (since I think it's not possible), to have at least something like make_array<1>({1, 2, 3}) where the rank is explicitly specified?
array_type::shape can be generated with std::extent with a bit of template meta programming:
template<typename Array, std::size_t... I>
auto extents_impl(const Array& a, std::index_sequence<I...>)
{
return std::array{std::extent_v<Array, I>...};
}
template<typename Array>
auto extents(const Array& a)
{
return extents_impl(a, std::make_index_sequence<std::rank_v<Array>>());
}
With this, make_array can be written as:
template <class Array, std::enable_if_t<std::is_same_v<std::remove_cv_t<std::remove_all_extents_t<Array>>, float>, int> = 0> // magic incantation :)
array_type<std::rank_v<Array>> make_array(Array const& a)
{
array_type<std::rank_v<Array>> ret {
.shape = extents(a),
.data = std::vector<float>(sizeof a / sizeof(float)),
};
std::memcpy(ret.data.data(), &a, sizeof a);
return ret;
}
I use memcpy to avoid potential pointer type aliasing violations and the technicality of iterating outside the bounds of sub array being UB according to strict interpretation of the standard.
...but that does not work for initializer list:
Add one overload:
template <std::size_t N>
array_type<1> make_array(float const (&a)[N])
{
return make_array<float[N]>(a);
}
Alternatively, you can specify the array type in the call:
make_array<float[2][3]>({{1, 2, 3},{4, 5, 6}});
This requires no overloads.
T (&&)[N] is suggested when you want a function template to accept a braced-init-list and deduce its length, furthermore, you can use T (&&)[M][N], T (&&)[M][N][O] ... to deduce the lengths of each rank (sadly, there is nothing like T (&&)[N]...[Z] to accept array with arbitrary rank).
so you can provide overloading function like this:
// attention that `T (&&)[N][M]` is `Y (&&)[N]` where `Y` is `T[M]`.
template<typename T, size_t N>
using Array1D_t = T[N];
template<typename T, size_t M, size_t N>
using Array2D_t = Array1D_t<Array1D_t<T, N>, M>;
template<typename T, size_t M, size_t N, size_t O>
using Array3D_t = Array1D_t<Array2D_t<T, N, O>, M>;
// ...
// assume the max rank is X.
template<typename T, size_t N>
array_type<1> make_array(Array1D_t<T, N>&& v);
template<typename T, size_t M, size_t N>
array_type<2> make_array(Array2D_t<T, M, N>&& v);
template<typename T, size_t M, size_t N, size_t O>
array_type<3> make_array(Array3D_t<T, M, N, O>&& v);
// ...
and then, it will be okay for make_array({1, 2, 3}), make_array({{1, 2, 3}, {4, 5, 6}}) ..., except the rank is larger than X.
Is there a way to have this make_array?
I don't think so (but, to be honest, I'm not able to demonstrate it's impossible).
Or (since I think it's not possible), to have at least something like make_array<1>({1, 2, 3}) where the rank is explicitly specified?
I don't see a way also in this case.
But... if you accept that the sizes are explicitly specified... you can write a recursive custom type traits to construct the final type
// declararion and ground case
template <typename T, std::size_t ...>
struct get_ranked_array
{ using type = T; };
// recursive case
template <typename T, std::size_t Dim0, std::size_t ... Dims>
struct get_ranked_array<T, Dim0, Dims...>
: public get_ranked_array<T[Dim0], Dims...>
{ };
template <typename T, std::size_t ... Dims>
using get_ranked_array_t = typename get_ranked_array<T, Dims...>::type;
and the make function simply become
template <std::size_t ... Dims>
auto make_ranked_array (get_ranked_array_t<float, Dims...> const & values)
-> array_type<sizeof...(Dims)>
{
return {{Dims...},
std::vector<float>(
reinterpret_cast<float const *>(values),
reinterpret_cast<float const *>(values) + (Dims * ...))};
}
You can use it as follows
auto arr1 = make_ranked_array<3u>({1.0f, 2.0f, 3.0f});
auto arr2 = make_ranked_array<3u, 2u>({ {1.0f, 2.0f, 3.0f},
{4.0f, 5.0f, 6.0f} });
If you want, you can maintain the deduction of the last size, but I don't know if it's a good idea.
The following is a full compiling C++17 example
#include <array>
#include <vector>
template <std::size_t N>
struct array_type
{
std::array<std::size_t, N> shape;
std::vector<float> data;
};
// declararion and ground case
template <typename T, std::size_t ...>
struct get_ranked_array
{ using type = T; };
// recursive case
template <typename T, std::size_t Dim0, std::size_t ... Dims>
struct get_ranked_array<T, Dim0, Dims...>
: public get_ranked_array<T[Dim0], Dims...>
{ };
template <typename T, std::size_t ... Dims>
using get_ranked_array_t = typename get_ranked_array<T, Dims...>::type;
template <std::size_t ... Dims>
auto make_ranked_array (get_ranked_array_t<float, Dims...> const & values)
-> array_type<sizeof...(Dims)>
{
return {{Dims...},
std::vector<float>(
reinterpret_cast<float const *>(values),
reinterpret_cast<float const *>(values) + (Dims * ...))};
}
int main ()
{
auto arr1 = make_ranked_array<3u>({1.0f, 2.0f, 3.0f});
auto arr2 = make_ranked_array<3u, 2u>({ {1.0f, 2.0f, 3.0f},
{4.0f, 5.0f, 6.0f} });
static_assert ( std::is_same_v<decltype(arr1), array_type<1>> );
static_assert ( std::is_same_v<decltype(arr2), array_type<2>> );
}
I am trying to write a function in order to generate arbitrarily nested vectors and initialize with the given specific value in C++. For example, auto test_vector = n_dim_vector_generator<2, long double>(static_cast<long double>(1), 1); is expected to create a "test_vector" object which type is std::vector<std::vector<long double>>. The content of this test_vector should as same as the following code.
std::vector<long double> vector1;
vector1.push_back(1);
std::vector<std::vector<long double>> test_vector;
test_vector.push_back(vector1);
The more complex usage of the n_dim_vector_generator function:
auto test_vector2 = n_dim_vector_generator<15, long double>(static_cast<long double>(2), 3);
In this case, the parameter static_cast<long double>(2) is as the data in vectors and the number 3 is as the push times. So, the content of this test_vector2 should as same as the following code.
std::vector<long double> vector1;
vector1.push_back(static_cast<long double>(2));
vector1.push_back(static_cast<long double>(2));
vector1.push_back(static_cast<long double>(2));
std::vector<std::vector<long double>> vector2;
vector2.push_back(vector1);
vector2.push_back(vector1);
vector2.push_back(vector1);
std::vector<std::vector<std::vector<long double>>> vector3;
vector3.push_back(vector2);
vector3.push_back(vector2);
vector3.push_back(vector2);
//...Totally repeat 15 times in order to create test_vector2
std::vector<...std::vector<long double>> test_vector2;
test_vector2.push_back(vector14);
test_vector2.push_back(vector14);
test_vector2.push_back(vector14);
The detail to implement n_dim_vector_generator function is as follows.
#include <iostream>
#include <vector>
template <typename T, std::size_t N>
struct n_dim_vector_type;
template <typename T>
struct n_dim_vector_type<T, 0> {
using type = T;
};
template <typename T, std::size_t N>
struct n_dim_vector_type {
using type = std::vector<typename n_dim_vector_type<T, N - 1>::type>;
};
template<std::size_t N, typename T>
typename n_dim_vector_type<T,N>::type n_dim_vector_generator(T t, unsigned int);
template <std::size_t N, typename T>
typename n_dim_vector_type<T, N>::type n_dim_vector_generator<N, T>(T input_data, unsigned int push_back_times) {
if (N == 0)
{
return std::move(input_data);
}
typename n_dim_vector_type<T, N>::type return_data;
for (size_t loop_number = 0; loop_number < push_back_times; loop_number++)
{
return_data.push_back(n_dim_vector_generator<N - 1, T>(input_data, push_back_times));
}
return return_data;
}
As a result, I got an error 'return': cannot convert from 'long double' to 'std::vector<std::vector<long double,std::allocator<long double>>,std::allocator<std::vector<long double,std::allocator<long double>>>>' I know that it caused by if (N == 0) block which is as the terminate condition to recursive structure. However, if I tried to edit the terminate condition into separate form.
template <typename T>
inline T n_dim_vector_generator<0, T>(T input_data, unsigned int push_back_times) {
return std::move(input_data);
}
template <std::size_t N, typename T>
typename n_dim_vector_type<T, N>::type n_dim_vector_generator<N, T>(T input_data, unsigned int push_back_times) {
typename n_dim_vector_type<T, N>::type return_data;
for (size_t loop_number = 0; loop_number < push_back_times; loop_number++)
{
return_data.push_back(n_dim_vector_generator<N - 1, T>(input_data, push_back_times));
}
return return_data;
}
The error 'n_dim_vector_generator': illegal use of explicit template arguments happened. Is there any better solution to this problem?
The develop environment is in Windows 10 1909 with Microsoft Visual Studio Enterprise 2019 Version 16.4.3
To achieve your specific mapping of:
auto test_vector = n_dim_vector_generator<2, long double>(2, 3)
to a 3x3 vector filled with 2's, your template can be a bit simpler if you take advantage of this vector constructor:
std::vector<std::vector<T>>(COUNT, std::vector<T>(...))
Since vector is copyable, this will fill COUNT slots with a different copy of the vector. So...
template <size_t N, typename T>
struct n_dim_vector_generator {
using type = std::vector<typename n_dim_vector_generator<N-1, T>::type>;
type operator()(T value, size_t size) {
return type(size, n_dim_vector_generator<N-1, T>{}(value, size));
}
};
template <typename T>
struct n_dim_vector_generator<0, T> {
using type = T;
type operator()(T value, size_t size) {
return value;
}
};
usage:
auto test_vector = n_dim_vector_generator<2, long double>{}(2, 3);
Demo: https://godbolt.org/z/eiDAUG
For the record, to address some concerns from the comments, C++ has an idiomatic, initializable, contiguous-memory class equivalent of a multi-dimension C array: a nested std::array:
std::array<std::array<long double, COLUMNS>, ROWS> test_array = { /*...*/ };
for (auto& row : test_array)
for (auto cell : row)
std::cout << cell << std::endl;
If you wanted to reduce the boilerplate to declare one, you can use a struct for that:
template <typename T, size_t... N>
struct multi_array;
template <typename T, size_t NFirst, size_t... N>
struct multi_array<T, NFirst, N...> {
using type = std::array<typename multi_array<T, N...>::type, NFirst>;
};
template <typename T, size_t NLast>
struct multi_array<T, NLast> {
using type = std::array<T, NLast>;
};
template <typename T, size_t... N>
using multi_array_t = typename multi_array<T, N...>::type;
Then to use:
multi_array_t<long double, ROWS, COLUMNS> test_array = { /*...*/ };
for (auto& row : test_array)
for (auto cell : row)
std::cout << cell << std::endl;
This is allocated on the stack, like a C array. That will eat up your stack space for a big array of course. But you can make a decorator range around std::unique_ptr to make a pointer to one a bit easier to access:
template <typename T, size_t... N>
struct dynamic_multi_array : std::unique_ptr<multi_array_t<T, N...>> {
using std::unique_ptr<multi_array_t<T, N...>>::unique_ptr;
constexpr typename multi_array_t<T, N...>::value_type& operator [](size_t index) { return (**this)[index]; }
constexpr const typename multi_array_t<T, N...>::value_type& operator [](size_t index) const { return (**this)[index]; }
constexpr typename multi_array_t<T, N...>::iterator begin() { return (**this).begin(); }
constexpr typename multi_array_t<T, N...>::iterator end() { return (**this).end(); }
constexpr typename multi_array_t<T, N...>::const_iterator begin() const { return (**this).begin(); }
constexpr typename multi_array_t<T, N...>::const_iterator end() const { return (**this).end(); }
constexpr typename multi_array_t<T, N...>::const_iterator cbegin() const { return (**this).cbegin(); }
constexpr typename multi_array_t<T, N...>::const_iterator cend() const { return (**this).cend(); }
constexpr typename multi_array_t<T, N...>::size_type size() const { return (**this).size(); }
constexpr bool empty() const { return (**this).empty(); }
constexpr typename multi_array_t<T, N...>::value_type* data() { return (**this).data(); }
constexpr const typename multi_array_t<T, N...>::value_type* data() const { return (**this).data(); }
};
(let the buyer beware if you use those methods with nullptr)
Then you can still brace-initialize a new expression and use it like a container:
dynamic_multi_array<long double, ROWS, COLUMNS> test_array {
new multi_array_t<long double, ROWS, COLUMNS> { /* ... */ }
};
for (auto& row : test_array)
for (auto cell : row)
std::cout << cell << std::endl;
Demo: https://godbolt.org/z/lUwVE_
I am implementing my static multi-dimentional vector class. I am using std::array as the underlying data type.
template <typename T, std::size_t N>
class Vector {
private:
std::array<T, N> data;
};
I want to make my class downwards-compatible, so I am writing this:
template <typename T, std::size_t N>
class Vector : public Vector<T, N-1>{
private:
std::array<T, N> data;
};
template <typename T>
class Vector<T, 0> {};
My goal is that when one instance is used in downwards-compatible mode, its underlying data should be able to be reliably accessed:
template<typename T, std::size_t N>
T& Vector<T, N>::operator[](int i) {
// Do boundary checking here
return this->data[i];
}
void foo(Vector<int, 3>& arg) {
arg[1] = 10;
}
Vector<int, 5> b;
foo(b);
// Now b[1] should be 10
There are two points here:
Vector<T, 5> should be accepted by foo(), Vector<T, 2> should be rejected.
Changes to b[0] through b[2] in foo() should pertain. b[3] and b[4] should not be accessible in foo().
How can I achieve that?
How about a simple read wrapper around std::array<> itself?
template<typename T, std::size_t N>
struct ArrayReader {
public:
// Intentionally implicit.
template<std::size_t SRC_LEN>
ArrayReader(std::array<T, SRC_LEN> const& src)
: data_(src.data()) {
static_assert(SRC_LEN >= N);
}
private:
T const* data_;
};
void foo(ArrayReader<float, 3>);
void bar() {
std::array<float, 4> a;
std::array<float, 2> b;
foo(a);
foo(b); //BOOM!
}
Of course, you can easily substitute std::array for your own type, this is just an example of the principle.
Have array keep the data, and then create additional non-owning class, e.g. array_view that will keep only a pointer. It will have generic constructor that accepts the array, and will have a static_assert to check the sizes.
Here's how I would approach this:
template <class Container, std::size_t size>
struct range_view
{
range_view(Container * p): container(p) { assert(size <= p->size()); }
auto & operator[](std::size_t i) { return (*container)[i]; }
private:
Container * container;
};
Then you simply define foo as:
template <class C>
void foo(range_view<C, 3> c)
{
c[1] = 1;
}
Here's something that is closest to what I think you would need.
Make Vector a viewer/user of the data, not the owner of the data.
#include <array>
template <typename T, std::size_t N, std::size_t I>
class Vector : public Vector<T, N, I-1>
{
public:
Vector(std::array<T, N>& arr) : Vector<T, N, I-1>(arr), arr_(arr) {}
T& operator[](int i) {
return arr_[i];
}
private:
std::array<T, N>& arr_;
};
template <typename T, std::size_t N>
class Vector<T, N, 0ul>
{
public:
Vector(std::array<T, N>& arr) : arr_(arr) {}
private:
std::array<T, N>& arr_;
};
void foo(Vector<int, 5, 3>& arg) {
arg[1] = 10;
// Can't find a way to make this a compile time error.
arg[3] = 10;
}
#include <iostream>
int main()
{
std::array<int, 5> arr;
Vector<int, 5, 5> b(arr);
foo(b);
std::cout << b[1] << std::endl;
}
Here's a demonstration of how to implement the Vector class that you tried in your question. At each level you only store 1 value instead of an array and that way when you compose all your N Arrays together you get space for N values. Of course then calling operator[] gets tricky, which is the meat of what I wanted to demonstrate.
#include <utility>
template <class T, std::size_t N>
struct Array : Array<T, N-1>
{
T & operator[](std::size_t i)
{
return const_cast<T&>((*const_cast<const Array*>(this))[i]);
}
const T & operator[](std::size_t i) const
{
return Get(i, std::make_index_sequence<N>());
}
template <std::size_t i>
const T & Geti() const
{
return static_cast<const Array<T, i+1>&>(*this).GetValue();
}
const T & GetValue() const { return value; }
template <std::size_t ... indices>
const T & Get(std::size_t i, std::integer_sequence<std::size_t, indices...>) const
{
using X = decltype(&Array::Geti<0>);
X getters[] = { &Array::Geti<indices>... };
return (this->*getters[i])();
}
template <std::size_t i, class = typename std::enable_if<(i <= N)>::type>
operator Array<T, i>&() { return (Array<T, i>&)*this; }
private:
T value;
};
template <class T>
struct Array<T, 0>{};
void foo(Array<float, 3> & a) { a[1] = 10; }
int main()
{
Array<float, 10> a;
foo(a);
}
I am thinking about following problem:
Let us have a merging function for merge arrays defined in following way:
// input is (const void*, size_t, const void*, size_t,...)
template<typename...ARGS>
MyArray Concatenation(ARGS...args)
And let us have couple of structs with static properties
struct A { static void* DATA; static size_t SIZE; };
struct B { static void* DATA; static size_t SIZE; };
struct C { static void* DATA; static size_t SIZE; };
I would like to have a method:
template<typename...ARGS>
MyArray AutoConcatenation();
Where ARGS should be structs with mentioned static interface.
Following methods should have the same output:
AutoConcatenation<A, B, C>();
Concatenation(A::DATA, A::SIZE, B::DATA, B::SIZE, C::DATA, C::SIZE);
My question is how to implement parameter pack expansion.
I tried:
// not working
template<typename...ARGS>
MyArray AutoConcatenation()
{
return Concatenation((ARGS::DATA, ARGS::SIZE)...);
}
What about expansions
ARGS::DATA... // Correct expansion of pointers
ARGS::SIZE... // Correct expansion of sizes
(ARGS::DATA, ARGS::SIZE)... // Seems to be expansion of sizes
Just info for advisors. I am looking for implementation of AutoConcatenation method, not for its redeclaration nor for redeclaration previous code, thank you.
A lazy solution using std::tuple:
make a tuple of DATA and SIZE for each element of the parameter pack,
flatten the list of tuples to one big tuple using std::tuple_cat,
apply the resulting tuple's elements to Concatenation by expanding a list of indexes in an std::index_sequence.
In the following code, the test harness is longer than the actual solution:
#include <cstddef>
#include <tuple>
#include <utility>
#include <iostream>
#include <typeinfo>
#include <type_traits>
struct MyArray { };
template<class... ARGS> MyArray Concatenation(ARGS... args)
{
// Just some dummy code for testing.
using arr = int[];
(void)arr{(std::cout << typeid(args).name() << ' ' << args << '\n' , 0)...};
return {};
}
struct A { static void* DATA; static std::size_t SIZE; };
struct B { static void* DATA; static std::size_t SIZE; };
struct C { static void* DATA; static std::size_t SIZE; };
// Also needed for testing.
void* A::DATA;
std::size_t A::SIZE;
void* B::DATA;
std::size_t B::SIZE;
void* C::DATA;
std::size_t C::SIZE;
// The useful stuff starts here.
template<class T, std::size_t... Is> MyArray concat_hlp_2(const T& tup, std::index_sequence<Is...>)
{
return Concatenation(std::get<Is>(tup)...);
}
template<class T> MyArray concat_hlp_1(const T& tup)
{
return concat_hlp_2(tup, std::make_index_sequence<std::tuple_size<T>::value>{});
}
template<class... ARGS> MyArray AutoConcatenation()
{
return concat_hlp_1(std::tuple_cat(std::make_tuple(ARGS::DATA, ARGS::SIZE)...));
}
int main()
{
AutoConcatenation<A, B, C>();
}
If you want to avoid std::tuple and std::tuple_cat (which can be heavy in terms of compile times), here's an alternative using indexes into arrays.
The testing code stays the same, this is just the juicy stuff:
template<std::size_t Size> const void* select(std::false_type, std::size_t idx,
const void* (& arr_data)[Size], std::size_t (&)[Size])
{
return arr_data[idx];
}
template<std::size_t Size> std::size_t select(std::true_type, std::size_t idx,
const void* (&)[Size], std::size_t (& arr_size)[Size])
{
return arr_size[idx];
}
template<std::size_t... Is> MyArray concat_hlp(std::index_sequence<Is...>,
const void* (&& arr_data)[sizeof...(Is) / 2], std::size_t (&& arr_size)[sizeof...(Is) / 2])
{
return Concatenation(select(std::bool_constant<Is % 2>{}, Is / 2, arr_data, arr_size)...);
}
template<class... ARGS> MyArray AutoConcatenation()
{
return concat_hlp(std::make_index_sequence<sizeof...(ARGS) * 2>{}, {ARGS::DATA...}, {ARGS::SIZE...});
}
Again a sequence of indexes twice the size of the original parameter pack, but we build separate arrays of DATA and SIZE and then use tag dispatching to select elements from one or the other depending on the parity of the current index.
This may not look as nice as the previous code, but it doesn't involve any template recursion (std::make_index_sequence is implemented using compiler intrinsics in modern compilers as far as I know) and cuts down on the number of template instantiations, so it should be faster to compile.
The select helper can be made constexpr by using arrays of pointers to the static members, but this turns out to be unnecessary in practice. I've tested MSVC 2015 U2, Clang 3.8.0 and GCC 6.1.0 and they all optimize this to a direct call to Concatenation (just like for the tuple-based solution).
I think the following is more elegant, and it illustrates the common recursive unpacking pattern. Finally, it does not perform any voodoo with memory layouts and tries to be idiomatic as far as C++ generic programming.
#include <iostream>
#include <string>
using namespace std;
// Handle zero arguments.
template <typename T = string>
T concat_helper() { return T(); }
// Handle one pair.
template <typename T = string>
T concat_helper(const T &first, size_t flen) { return first; }
// Handle two or more pairs. Demonstrates the recursive unpacking pattern
// (very common with variadic arguments).
template <typename T = string, typename ...ARGS>
T concat_helper(const T &first, size_t flen,
const T &second, size_t slen,
ARGS ... rest) {
// Your concatenation code goes here. We're assuming we're
// working with std::string, or anything that has method length() and
// substr(), with obvious behavior, and supports the + operator.
T concatenated = first.substr(0, flen) + second.substr(0, slen);
return concat_helper<T>(concatenated, concatenated.length(), rest...);
}
template <typename T, typename ...ARGS>
T Concatenate(ARGS...args) { return concat_helper<T>(args...); }
template <typename T>
struct pack {
T data;
size_t dlen;
};
template <typename T>
T AutoConcatenate_helper() { return T(); }
template <typename T>
T AutoConcatenate_helper(const pack<T> *packet) {
return packet->data;
}
template <typename T, typename ...ARGS>
T AutoConcatenate_helper(const pack<T> *first, const pack<T> *second,
ARGS...rest) {
T concatenated = Concatenate<T>(first->data, first->dlen,
second->data, second->dlen);
pack<T> newPack;
newPack.data = concatenated;
newPack.dlen = concatenated.length();
return AutoConcatenate_helper<T>(&newPack, rest...);
}
template <typename T, typename ...ARGS>
T AutoConcatenate(ARGS...args) {
return AutoConcatenate_helper<T>(args...);
}
int main() {
pack<string> first;
pack<string> second;
pack<string> third;
pack<string> last;
first.data = "Hello";
first.dlen = first.data.length();
second.data = ", ";
second.dlen = second.data.length();
third.data = "World";
third.dlen = third.data.length();
last.data = "!";
last.dlen = last.data.length();
cout << AutoConcatenate<string>(&first, &second, &third, &last) << endl;
return 0;
}
We're neither changing the declaration of Concatenate<>(), nor that of AutoConcatenate<>(), as required. We're free to implement AutoConcatenate<>(), as we did, and we assume that there is some implementation of Concatenate<>() (we provided a simple one for a working example).
Here is possible solution:
enum Delimiters { Delimiter };
const void* findData(size_t count) { return nullptr; }
template<typename...ARGS>
const void* findData(size_t count, size_t, ARGS...args)
{
return findData(count, args...);
}
template<typename...ARGS>
const void* findData(size_t count, const void* data, ARGS...args)
{
return count ? findData(count - 1, args...) : data;
}
template<typename...ARGS>
MyArray reordered(size_t count, Delimiters, ARGS...args)
{
return Concatenate(args...);
}
template<typename...ARGS>
MyArray reordered(size_t count, const void* size, ARGS...args)
{
return reordered(count, args...);
}
template<typename...ARGS>
MyArray reordered(size_t count, size_t size, ARGS...args)
{
return reordered(count + 1, args..., findData(count, args...), size);
}
template<typename...ARGS>
MyArray AutoConcatenate()
{
return reordered(0, ARGS::LAYOUT_SIZE..., ARGS::LAYOUT..., Delimiter);
}
If you know more elegant way, please let me know.
EDIT
One little more elegant way is to keep function argument count as template parameter...
I am trying to define a hasher for vectors. I have a primary template for simple types, and a specialization for classes which have operator().
However, I get an error template parameters not deducible in partial specialization. Could somebody please point out why?
template <typename T> struct hash<vector<T>>
{
size_t operator()(const vector<T> &x) const
{
size_t res = 0;
for(const auto &v:x) {
boost::hash_combine(res,v);
}
return res;
}
};
template <typename T> struct hash<vector<enable_if_t<true_t<decltype(sizeof(declval<T>()()))>::value, T>>>
{
size_t operator()(const vector<T> &x) const
{
size_t res = 0;
for(const auto &v:x) {
boost::hash_combine(res,v());
}
return res;
}
};
I don't really like partial specialization here, especially as it causes code duplication.
template <typename T>
struct hash<vector<T>>
{
template<class T>
static auto call_if_possible(const T& t, int) -> decltype(t()) { return t(); }
template<class T>
static auto call_if_possible(const T& t, ...) -> decltype(t) { return t; }
size_t operator()(const vector<T> &x) const
{
size_t res = 0;
for(const auto &v:x) {
boost::hash_combine(res,call_if_possible(v, 0));
}
return res;
}
};
(If this hash is actually std::hash, then the answer is "don't do it". You may not specialize a standard library template unless the specialization depends on a user-defined type.)
In your second template specialization T inside the enable_if is in a non-deduced context, so the compiler cannot deduce it. It is effectively the same as:
template<typename T>
struct Identity
{
using type = T;
}
template<typename T>
void f(typename Identity<T>::type x){} // T is non-deducible
Moreover, you have a "double" non-deducible context, because an expression containing T inside a decltype is non-deducible.
for what it's worth, here was my first attempt - handles ADL-lookup of hash_value as well as the one in the boost namespace:
#include <iostream>
#include <boost/functional/hash.hpp>
#include <vector>
template<class T>
struct hash_value_defined
{
template<class U> static auto boost_hash_value_test(U*p) -> decltype(boost::hash_value(*p),
void(),
std::true_type());
template<class U> static auto adl_hash_value_test(U*p) -> decltype(hash_value(*p),
void(),
std::true_type());
template<class U> static std::false_type boost_hash_value_test(...);
template<class U> static std::false_type adl_hash_value_test(...);
static constexpr bool boost_value = decltype(boost_hash_value_test<T>(nullptr))::value;
static constexpr bool adl_value = decltype(adl_hash_value_test<T>(nullptr))::value;
static constexpr bool value = boost_value or adl_value;
};
template<class T, class A, std::enable_if_t<hash_value_defined<T>::value> * = nullptr >
size_t hash_value(const std::vector<T, A>& v) {
size_t seed = 0;
for(const auto& e : v) {
boost::hash_combine(seed, e);
}
return seed;
}
int main()
{
using namespace std;
vector<int> x { 1, 2, 3, 4, 5 };
auto h = hash_value(x);
cout << h << endl;
return 0;
}