Differentiate between 1D and 2D container in template class constructor (SFINAE) - c++

So, I have a class, which has an array of arrays as a private member. I wish to have two constructors for each case (1D or 2D). But of course their declaration happens to be the same, so template deduction can't do its job without me doing something about it. Here's the code:
Edit: I also need it to work with STL containers like vector or C++ array. That is why I am overcomplicating and not going with the "arrays" fix.
#include <iostream>
#include <array>
template<class T, std::size_t rows_t, std::size_t cols_t>
class test
{
private:
std::array<std::array<T, cols_t>, rows_t> _data;
public:
auto begin() { return this->_data.begin(); }
auto end() { return this->_data.end(); }
//CONSTRUCTOR
template<class type_t>
test(const type_t &arr)
{
std::size_t j = 0;
for (const auto &num : arr)
this->_data[0][j++] = num;
}
template<class type_t>
test(const type_t &arr)
{
std::size_t i = 0;
for (const auto &el : arr)
{
std::size_t j = 0;
for (const auto &num : el)
this->_data[i][j++] = num;
++i;
}
}
};
int main()
{
double arr[3] = { 1, 2, 3 };
double arr2[2][2] = { {1, 2}, {3, 4} };
test<double, 1, 3> obj = arr;
test<double, 2, 2> obj2 = arr2;
for (const auto &i : obj2)
{
for (const auto &j : i)
std::cout << j << " ";
std::cout << std::endl;
}
std::cin.get();
}
Note: I've been reading about enable_if, but I don't quite understand how it works. Can it be done with that?

The constructors should not be the same, but you have only provided the most generic matching possible.
SFINAE is not necessary here. Just provide a constructor for a 1D array, and a separate constructor for a 2D array:
template <typename T2, std::size_t N>
test( const T2 (&a)[N] )
{
...
}
template <typename T2, std::size_t M, std::size_t N>
test( const T2 (&a)[M][N] )
{
...
}
Another note: POSIX reserves typenames ending with "_t", so it is typically a good idea to avoid them in your own code. (Obnoxious, I know.) Standard C++ will use Camel Case of the form: RowsType, etc, and then typedef a rows_type for users of the class.
Notice, however, that rows_t is not actually a type -- it is a value. A better name would be something like NRows.
Hope this helps.

First, you have to "teach" the compiler what's 2D and what's not. Hence, you have to define something like the following type trait:
template<typename T>
struct is2D : public std::false_type {};
template<typename T, std::size_t N, std::size_t M>
struct is2D<std::array<std::array<T, M>, N>> : std::true_type {};
template<typename T>
struct is2D<std::vector<std::vector<T>>> : std::true_type {};
template<typename T, std::size_t N, std::size_t M>
struct is2D<T[N][M]> : std::true_type {};
Then you could set up your class definition in the following way:
template<class T, std::size_t rows_t, std::size_t cols_t>
class test{
std::array<std::array<T, cols_t>, rows_t> _data;
template<class type_t>
std::enable_if_t<!is2D<type_t>::value, void>
test_init(type_t const &arr) {
std::size_t j = 0;
for (const auto &num : arr) _data[0][j++] = num;
}
template<class type_t>
std::enable_if_t<is2D<type_t>::value, void>
test_init(type_t const &arr) {
std::size_t i = 0;
for(const auto &el : arr) {
std::size_t j = 0;
for (const auto &num : el) _data[i][j++] = num;
++i;
}
}
public:
auto &operator[](const std::size_t &i) { return this->_data[i]; }
auto begin() { return this->_data.begin(); }
auto end() { return this->_data.end(); }
//CONSTRUCTOR
template<class type_t> test(type_t const &arr) { test_init(arr); }
};
LIVE DEMO

Related

C++, change data type of all the elements into a nested C array

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() };
}

Generate Arbitrarily Nested Vectors in C++

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_

c++ constexpr and reference

Because of C++14 does not have constexpr array, I tried to create my own.
Here is the full code :
template<typename T, std::size_t N>
class constexpr_array {
public:
constexpr constexpr_array() = default;
template<typename U>
constexpr constexpr_array(std::initializer_list<U> values) :
m_array{} {
const U *it = values.begin();
for(std::size_t i{0}; i < N; ++i) {
m_array[i] = *it++;
}
}
constexpr constexpr_array(const constexpr_array&) = default;
constexpr constexpr_array(constexpr_array&&) = default;
constexpr T& operator[](std::size_t index) {
return m_array[index];
}
constexpr const T& operator[](std::size_t index) const {
return m_array[index];
}
constexpr const constexpr_array *operator&() const {
return this;
}
private:
T m_array[N];
};
It works well, however I wonder why it is not allowed, (maybe only by MSVC?) to have a initializer_list constructor like that :
template<typename U>
constexpr constexpr_array(std::initializer_list<U> values) :
m_array{} {
const U *it = values.begin();
for(auto &v : m_array) {
v = *it++;
}
}
I got c2131-expression-did-not-evaluate-to-a-constant in Visual Studio 2017. The error is at line for(auto &v : m_array).
Here is a sample to try :
template<std::size_t N, const constexpr_array<std::size_t, N> *indices>
struct array_to_index_sequence {
template<typename Indices = std::make_index_sequence<N>>
struct as_index_sequence{};
template<std::size_t ...Is>
struct as_index_sequence<std::index_sequence<Is...>> : std::index_sequence<(*indices)[Is]...>{};
};
template<int... I>
struct a {
static constexpr constexpr_array<std::size_t, 3> compute() {
return {I...};
}
static constexpr constexpr_array<std::size_t, 3> t = compute();
using i = typename array_to_index_sequence<3, &t>::template as_index_sequence<>;
};
#include <string>
template<std::size_t ...I>
void f(std::tuple<int, std::string, double, double> t, std::index_sequence<I...>) {
int a[] = {0, (std::cout << std::get<I>(t) << std::endl, 0)...};
}
int main()
{
f(std::make_tuple(5, "lolll", 1.0, 2.0), typename a<2, 1, 0>::i{});
return 0;
}

Make longer std::array accessible as if it's shorter

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);
}

constexpr initialization of array to sort contents

This is a bit of a puzzle rather than a real-world problem, but I've gotten into a situation where I want to be able to write something that behaves exactly like
template<int N>
struct SortMyElements {
int data[N];
template<typename... TT>
SortMyElements(TT... tt) : data{ tt... }
{
std::sort(data, data+N);
}
};
int main() {
SortMyElements<5> se(1,4,2,5,3);
int se_reference[5] = {1,2,3,4,5};
assert(memcmp(se.data, se_reference, sizeof se.data) == 0);
}
except that I want the SortMyElements constructor to be constexpr.
Obviously this is possible for fixed N; for example, I can specialize
template<>
struct SortMyElements<1> {
int data[1];
constexpr SortMyElements(int x) : data{ x } {}
};
template<>
struct SortMyElements<2> {
int data[2];
constexpr SortMyElements(int x, int y) : data{ x>y?y:x, x>y?x:y } {}
};
But how do I generalize this into something that will work for any N?
Please notice that the array elements have to come from the actual values of the arguments, not from template non-type arguments; my elements come from constexpr expressions that, despite being evaluated at compile-time, reside firmly inside the "value system", rather than the "type system". (For example, Boost.MPL's sort works strictly within the "type system".)
I've posted a working "answer", but it's too inefficient to work for N > 6. I'd like to use this with 2 < N < 50 or thereabouts.
(P.S.— Actually what I'd really like to do is shuffle all the zeroes in an array to the end of the array and pack the nonzero values toward the front, which might be easier than full-on sorting; but I figure sorting is easier to describe. Feel free to tackle the "shuffle zeroes" problem instead of sorting.)
It's ugly, and probably not the best way to sort in a constant expression (because of the required instantiation depth).. but voilà, a merge sort:
Helper type, returnable array type with constexpr element access:
#include <cstddef>
#include <iterator>
#include <type_traits>
template<class T, std::size_t N>
struct c_array
{
T arr[N];
constexpr T const& operator[](std::size_t p) const
{ return arr[p]; }
constexpr T const* begin() const
{ return arr+0; }
constexpr T const* end() const
{ return arr+N; }
};
template<class T>
struct c_array<T, 0> {};
append function for that array type:
template<std::size_t... Is>
struct seq {};
template<std::size_t N, std::size_t... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...> {};
template<std::size_t... Is>
struct gen_seq<0, Is...> : seq<Is...> {};
template<class T, std::size_t N, class U, std::size_t... Is>
constexpr c_array<T, N+1> append_impl(c_array<T, N> const& p, U const& e,
seq<Is...>)
{
return {{p[Is]..., e}};
}
template<class T, std::size_t N, class U>
constexpr c_array<T, N+1> append(c_array<T, N> const& p, U const& e)
{
return append_impl(p, e, gen_seq<N>{});
}
Merge sort:
template<std::size_t Res, class T, class It, std::size_t Accum,
class = typename std::enable_if<Res!=Accum, void>::type >
constexpr c_array<T, Res> c_merge(It beg0, It end0, It beg1, It end1,
c_array<T, Accum> const& accum)
{
return
beg0 == end0 ? c_merge<Res>(beg0 , end0, beg1+1, end1, append(accum, *beg1)) :
beg1 == end1 ? c_merge<Res>(beg0+1, end0, beg1 , end1, append(accum, *beg0)) :
*beg0 < *beg1 ? c_merge<Res>(beg0+1, end0, beg1 , end1, append(accum, *beg0))
: c_merge<Res>(beg0 , end0, beg1+1, end1, append(accum, *beg1));
}
template<std::size_t Res, class T, class It, class... Dummies>
constexpr c_array<T, Res> c_merge(It beg0, It end0, It beg1, It end1,
c_array<T, Res> const& accum, Dummies...)
{
return accum;
}
template<class T, std::size_t L, std::size_t R>
constexpr c_array<T, L+R> c_merge(c_array<T, L> const& l,
c_array<T, R> const& r)
{
return c_merge<L+R>(l.begin(), l.end(), r.begin(), r.end(),
c_array<T, 0>{});
}
template<class T>
using rem_ref = typename std::remove_reference<T>::type;
template<std::size_t dist>
struct helper
{
template < class It >
static constexpr auto merge_sort(It beg, It end)
-> c_array<rem_ref<decltype(*beg)>, dist>
{
return c_merge(helper<dist/2>::merge_sort(beg, beg+dist/2),
helper<dist-dist/2>::merge_sort(beg+dist/2, end));
}
};
template<>
struct helper<0>
{
template < class It >
static constexpr auto merge_sort(It beg, It end)
-> c_array<rem_ref<decltype(*beg)>, 0>
{
return {};
}
};
template<>
struct helper<1>
{
template < class It >
static constexpr auto merge_sort(It beg, It end)
-> c_array<rem_ref<decltype(*beg)>, 1>
{
return {*beg};
}
};
template < std::size_t dist, class It >
constexpr auto merge_sort(It beg, It end)
-> c_array<rem_ref<decltype(*beg)>, dist>
{
return helper<dist>::merge_sort(beg, end);
}
Helpers for usage example:
template<class T, std::size_t N>
constexpr std::size_t array_size(T (&arr)[N]) { return N; }
template<class T, std::size_t N>
constexpr T* c_begin(T (&arr)[N]) { return arr; }
template<class T, std::size_t N>
constexpr T* c_end(T (&arr)[N]) { return arr+N; }
Usage example:
constexpr int unsorted[] = {5,7,3,4,1,8,2,9,0,6,10}; // odd number of elements
constexpr auto sorted = merge_sort<array_size(unsorted)>(c_begin(unsorted),
c_end(unsorted));
#include <iostream>
int main()
{
std::cout << "unsorted: ";
for(auto const& e : unsorted) std::cout << e << ", ";
std::cout << '\n';
std::cout << "sorted: ";
for(auto const& e : sorted) std::cout << e << ", ";
std::cout << '\n';
}
Output:
unsorted: 5, 7, 3, 4, 1, 8, 2, 9, 0, 6, 10,
sorted: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
I know that this is an old question but as we have C++14 (and C++17 soon), and since C++14 constexpr rules aren't so restricted, and, for sure, couple of people will find your question in google, here is how quicksort (and of course other algorithms) can be done since C++14.
(big credits to #dyp for constexpr array)
#include <utility>
#include <cstdlib>
template<class T>
constexpr void swap(T& l, T& r)
{
T tmp = std::move(l);
l = std::move(r);
r = std::move(tmp);
}
template <typename T, size_t N>
struct array
{
constexpr T& operator[](size_t i)
{
return arr[i];
}
constexpr const T& operator[](size_t i) const
{
return arr[i];
}
constexpr const T* begin() const
{
return arr;
}
constexpr const T* end() const
{
return arr + N;
}
T arr[N];
};
template <typename T, size_t N>
constexpr void sort_impl(array<T, N> &array, size_t left, size_t right)
{
if (left < right)
{
size_t m = left;
for (size_t i = left + 1; i<right; i++)
if (array[i]<array[left])
swap(array[++m], array[i]);
swap(array[left], array[m]);
sort_impl(array, left, m);
sort_impl(array, m + 1, right);
}
}
template <typename T, size_t N>
constexpr array<T, N> sort(array<T, N> array)
{
auto sorted = array;
sort_impl(sorted, 0, N);
return sorted;
}
constexpr array<int, 11> unsorted{5,7,3,4,1,8,2,9,0,6,10}; // odd number of elements
constexpr auto sorted = sort(unsorted);
#include <iostream>
int main()
{
std::cout << "unsorted: ";
for(auto const& e : unsorted)
std::cout << e << ", ";
std::cout << '\n';
std::cout << "sorted: ";
for(auto const& e : sorted)
std::cout << e << ", ";
std::cout << '\n';
}
LIVE DEMO
A bit late to the party, but a much better and simpler implementation is the following comb_sort implementation.
template<typename Array>
constexpr void comb_sort_impl ( Array & array_ ) noexcept {
using size_type = typename Array::size_type;
size_type gap = array_.size ( );
bool swapped = false;
while ( ( gap > size_type { 1 } ) or swapped ) {
if ( gap > size_type { 1 } ) {
gap = static_cast<size_type> ( gap / 1.247330950103979 );
}
swapped = false;
for ( size_type i = size_type { 0 }; gap + i < static_cast<size_type> ( array_.size ( ) ); ++i ) {
if ( array_ [ i ] > array_ [ i + gap ] ) {
auto swap = array_ [ i ];
array_ [ i ] = array_ [ i + gap ];
array_ [ i + gap ] = swap;
swapped = true;
}
}
}
}
template<typename Array>
constexpr Array sort ( Array array_ ) noexcept {
auto sorted = array_;
comb_sort_impl ( sorted );
return sorted;
}
int main ( ) {
constexpr auto sorted = sort ( std::array<int, 8> { 6, 8, 0, 1, 5, 9, 2, 7 } );
for ( auto i : sorted )
std::cout << i << ' ';
std::cout << std::endl;
return EXIT_SUCCESS;
}
Output: 0 1 2 5 6 7 8 9
Why better, it's [the algorithm] often as good as insertion sort, but is non-recursive, which means it will work on any size arrays (at least not limited by the recursive depth).
Well, I got my inefficient version to compile, at least with Clang on OSX. Here's the code.
However, while it's tolerably fast for five elements, on my laptop it takes 0.5 seconds to sort six elements and 7 seconds to sort seven elements. (Catastrophically varying performance, too, depending on whether the items are almost-sorted or reverse-sorted.) I didn't even try timing eight. Clearly, this doesn't scale to the kind of things I want to do with it. (I'd say 50 elements is a reasonable upper bound for my contrived use-case, but 6 is unreasonably tiny.)
#include <cstring>
#include <cassert>
template<int...>
struct IntHolder {};
// Now let's make a consecutive range of ints from [A to B).
template<int A, int B, int... Accum>
struct IntRange_ : IntRange_<A+1, B, Accum..., A> {};
template<int A, int... Accum>
struct IntRange_<A, A, Accum...> {
using type = IntHolder<Accum...>;
};
template<int A, int B>
using IntRange = typename IntRange_<A,B>::type;
// And a helper function to do what std::min should be doing for us.
template<typename... TT> constexpr int min(TT...);
constexpr int min(int i) { return i; }
template<typename... TT> constexpr int min(int i, TT... tt) { return i < min(tt...) ? i : min(tt...); }
template<int N>
struct SortMyElements {
int data[N];
template<int... II, typename... TT>
constexpr SortMyElements(IntHolder<II...> ii, int minval, int a, TT... tt) : data{
( a==minval ? a : SortMyElements<N>(ii, minval, tt..., a).data[0] ),
( a==minval ? SortMyElements<N-1>(tt...).data[II] : SortMyElements<N>(ii, minval, tt..., a).data[II+1] )...
} {}
template<typename... TT>
constexpr SortMyElements(TT... tt) : SortMyElements(IntRange<0,sizeof...(tt)-1>(), min(tt...), tt...) {}
};
template<>
struct SortMyElements<1> {
int data[1];
constexpr SortMyElements(int x) : data{ x } {}
constexpr SortMyElements(IntHolder<>, int minval, int x) : SortMyElements(x) {}
};
static_assert(SortMyElements<5>(5,2,1,3,1).data[0] == 1, "");
static_assert(SortMyElements<5>(5,2,1,3,1).data[1] == 1, "");
static_assert(SortMyElements<5>(5,2,1,3,1).data[2] == 2, "");
static_assert(SortMyElements<5>(5,2,1,3,1).data[3] == 3, "");
static_assert(SortMyElements<5>(5,2,1,3,1).data[4] == 5, "");
char global_array[ SortMyElements<5>(1,4,2,5,3).data[2] ];
static_assert(sizeof global_array == 3, "");
int main() {
SortMyElements<5> se(1,4,2,5,3);
int se_reference[5] = {1,2,3,4,5};
assert(memcmp(se.data, se_reference, sizeof se.data) == 0);
}
UPDATE: I haven't figured out how to do a fast mergesort (although DyP's answer looks potentially feasible to me). However, this morning I did solve my original puzzle-problem of shuffling zeroes to the end of an array! I used a recursive partition-and-merge algorithm; the code looks like this.
Starting with C++20, all you need to change in your example is to add constexpr to the constructor. That is, in C++20, std::sort is in fact constexpr.