Creating a C array wrapper (a better std::array?) - c++

Here's a simple 2-dimensional C-style array example:
int c[2][2] = {
{ 1, 2 },
{ 3, 4 }
};
If I want this to be std::array, I have to use this:
std::array<std::array<int, 2>, 2> c = {
{ { 1, 2 } },
{ { 3, 4 } }
};
The declaration is more involved, and I have to use extra { } for initialization.
Is it possible to create a C-style array wrapper, with which I can do this?
my_array<int, 2, 2> c = {
{ 1, 2 },
{ 3, 4 }
};
So the declaration is simpler, and there is no need of the extra { }.
If creating something like this is possible, would this solution have some drawbacks compared to std::array?
I've managed to do this:
template <typename T, std::size_t ...S>
struct ArrayTypeHelper;
template <typename T, std::size_t HEAD, std::size_t ...TAIL>
struct ArrayTypeHelper<T, HEAD, TAIL...> {
using Type = typename ArrayTypeHelper<T, TAIL...>::Type[HEAD];
};
template <typename T, std::size_t HEAD>
struct ArrayTypeHelper<T, HEAD> {
using Type = T[HEAD];
};
template <typename T, std::size_t ...S>
struct my_array {
typename ArrayTypeHelper<T, S...>::Type data;
};
But this still needs extra { }:
my_array<int, 2, 2> b = { {
{ 1, 2 },
{ 3, 4 }
} };

The problem is that a c-array member is always viewed by the brace elision algorithm as a single entity. If the inner initializer begins with a brace , the algorithm expects an initializer list with one element. So the solution is to define the carray wrapper in such a way that the brace elision algorithm knows how many elements are aggregated by the wrapper.
To do this, the only solution I see is to simulate an array through multiple inheritance:
#include <utility>
using namespace std;
template<class T,size_t DeDuplicate>
struct holder{
T val;
};
template<class T,class IndSeq>
struct carray_wrapper_b;
template<class T,size_t...Is>
struct carray_wrapper_b<T,index_sequence<Is...>>
:holder<T,Is>...
{ };
template<class T,size_t I,size_t...Is>
struct make_carray_{
using type = carray_wrapper_b<typename make_carray_<T,Is...>::type
,make_index_sequence<I>>;
};
template<class T,size_t I>
struct make_carray_<T,I>{
using type = carray_wrapper_b<T,make_index_sequence<I>>;
};
template<class T,size_t...Is>
using carray_wrapper = typename make_carray_<T,Is...>::type;
carray_wrapper<int,2,2> x = { {1,2},{3,4}};
carray_wrapper<int,2,2> y = { 1,2,3,4};
This array wrapper can also be used for initialization purpose only:
template<class T,size_t I,size_t...Is>
struct carray{
carray(initializer_list<T>);
carray(initializer_list<carray_wrapper<T,Is...>>);
};
carray<int,2,2,3> arr{1,2,3,4,5,6,7};
carray<int,2,2,3> arr2{{1,2},{3,4}};
carray<int,2,2,3> arr3{{{1,2},{3,4}},{1,2}};

Related

How can I disambiguate initializer_lists for constructor calls?

I want to be able to initialize container with a brace init list and not have to specify types. This is more difficult as I have nested initializers. Now I'm at the point where it almost works, but there are two overloads for initializer_list in the variant wrapper and the compiler can't figure out which to use.
Actually in this case the call is not even ambiguous as only class keyval takes a string as its first argument, which means the keyval overload of the variant wrapper should be chosen. But how can I tell this to the compiler?
My code (CompilerExplorer):
#include <string>
#include <variant>
template <typename... T> struct vwrapper;
using val = vwrapper<std::monostate, struct array, struct obj, int, bool>;
template <typename... T>
struct vwrapper : public std::variant<T...>
{
vwrapper(int);
vwrapper(bool);
vwrapper(std::initializer_list<struct keyval>);
vwrapper(std::initializer_list<val>);
// vwrapper(obj);
// vwrapper(array);
vwrapper(std::monostate);
};
struct obj
{
obj() = default;
obj(std::initializer_list<struct keyval> init) {
}
};
struct array
{
array() = default;
array(std::initializer_list<val> init) {
}
};
struct keyval
{
keyval() = default;
keyval(std::string, val);
};
template <typename... T>
vwrapper<T...>::vwrapper(int) {}
template <typename... T>
vwrapper<T...>::vwrapper(bool) {}
template <typename... T>
vwrapper<T...>::vwrapper(std::initializer_list<val>) {}
template <typename... T>
vwrapper<T...>::vwrapper(std::initializer_list<struct keyval>) {}
// template <typename... T>
// vwrapper<T...>::vwrapper(obj) {}
// template <typename... T>
// vwrapper<T...>::vwrapper(array) {}
keyval::keyval(std::string str, val value) { }
struct container : public array, public obj
{
using array::array;
using obj::obj;
};
int main()
{
container some_container = { 1, 2, true, false, 2, { { "hello", 2 }, { "mogli", true }}};
}
A minimal version of your snippet is
#include<initializer_list>
struct val
{
val(int);
val(bool);
val(std::initializer_list<struct keyval>);
val(std::initializer_list<val>);
};
struct keyval
{
keyval(const char*, val);
};
struct container
{
container(std::initializer_list<val>);
container(std::initializer_list<keyval>);
};
void foo()
{
container{{"", 2}, {"", true}};
}
At which point the ambiguity presents itself: a string literal is convertible to a bool. Both val{"", 2} and keyval{"", 2} are valid expressions, with the first being interpreted as
val{std::initializer_list<val>{val{bool("")}, val{2}}}
To avoid the ambiguity, force the type to be keyval
container{keyval{"", 2}, {"", true}};

How to have variadic templates with a type and a size?

Just for fun I am trying to overload a struct, one for a std::array<T,SIZE>, one for a std::vector<T>, and a std::unordered_map<T,U>. So I did the following:
template<typename... T>
struct Cont;
template<typename T, std::size_t SIZE>
struct Cont<T,SIZE>
{
Cont(std::string n) : name(n){}
std::string name;
std::array<T,SIZE> array;
};
template<typename T>
struct Cont<T>
{
Cont(std::string n) : name(n){}
std::string name;
std::vector<T> vector;
};
template<typename T, typename U>
struct Cont<T,U>
{
Cont(std::string n) : name(n){}
std::string name;
std::unordered_map<T,U> unordered_map;
};
However, when I try to compile it, I get the error expected a type, got SIZE. Which I totally understand is because typename... T is expecting a type and not a std::size_t. So I tried:
template<std::size_t SIZE, typename... T>
struct Cont;
template<typename... T>
struct Cont;
Which also doesn't work since I am then redefining and not overloading like I originally thought I was doing. I also realize, that I can do:
template<std::size_t SIZE, typename... T>
struct Cont;
However I am trying to keep this as clean as possible in the sense when I declare them I want to be able to do:
int main()
{
Cont<int,5> myArray("myArray");
myArray.array = {1,2,3,4,5};
Cont<int> myVector("myVector");
myVector.vector = {1,2,3,4};
Cont<int,int> myMap("myMap");
myMap.unordered_map[0] = 2;
}
Is there a way to either overload the template? (I assume this is no) or construct a template in a way that says I'm either typename... T or typename T, std::size_t SIZE?
I'm not sure how to get exactly what you want. Maybe a more clever user has an exact solution. But one alternative might be to just have a type parameter pack for your template and use std::integral_constants for your numeric template arguments. Then you could specialize for each case. For example :
#include <array>
#include <type_traits>
#include <unordered_map>
#include <vector>
template<class...>
struct Cont;
template<class T, class U, U S>
struct Cont<T, std::integral_constant<U, S>>
{
Cont(const char *) {};
std::array<T, S> array;
};
template<class T>
struct Cont<T>
{
Cont(const char *) {};
std::vector<T> vector;
};
template<class T>
struct Cont<T, T>
{
Cont(const char *) {};
std::unordered_map<T, T> unordered_map;
};
int main()
{
Cont<int,std::integral_constant<int, 5>> myArray("myArray");
myArray.array = {1,2,3,4,5};
Cont<int> myVector("myVector");
myVector.vector = {1,2,3,4};
Cont<int,int> myMap("myMap");
myMap.unordered_map[0] = 2;
}
You could make it be Cont<std::array<int, 5>>, and then have a template<typename T, std::size_t SIZE> struct Cont<std::array<T, SIZE>>; specialisation, but that breaks the pattern of the other types.
Another thing you can have is, in addition to making 5 into a type by wrapping it in a std::integral_constant, make a pair of overloaded helper functions to automatically do it for you:
template<typename... T>
Cont<T...> make_cont(std::string name) {
return { std::move(name) };
}
template<typename T, std::size_t SIZE>
Cont<T, std::integral_constant<std::size_t, SIZE>> make_cont(std::string name) {
return { std::move(name) };
}
int main() {
auto myArray = make_cont<int, 5>("myArray");
myArray.array = {1,2,3,4,5};
auto myVector = make_cont<int>("myVector");
myVector.vector = {1,2,3,4};
auto myMap = make_cont<int, int>("myMap");
myMap.unordered_map[0] = 2;
}
If you declare the array version like this
template <typename T, std::size_t SIZE>
struct Cont<std::array<T, SIZE>>
{
Cont(std::string n) : name(n) {}
std::string name;
std::array<T, SIZE> array;
};
Then in main you can use it like this:
int main() {
Cont<std::array<int, 5>> myArray("myArray");
myArray.array = {1, 2, 3, 4, 5};
Cont<int> myVector("myVector");
myVector.vector = {1, 2, 3, 4};
Cont<int, int> myMap("myMap");
myMap.unordered_map[0] = 2;
std::cout << "myArray " << myArray.array[0] << std::endl;
// myArray 1
std::cout << "myVector " << myVector.vector[0] << std::endl;
// myVector 1
std::cout << "myMap " << myMap.unordered_map[0] << std::endl;
// myMap 2
}
I think this is the functionality you are looking for and it's is clean and readable.
This works because std::array<int, 5> names a type that template <typename... T> is expecting and it contains the std::SIZE_T information you need to define a std::array.

Passing std::array argument with size restricted to expandable set of sizes

How would one best implement a single function that accepts two std::array<int, [size]> arguments, each with a size constrained by a corresponding set of values known at compile-time?
The function must only accept arrays with sizes derived from a given set (enum/macro/etc)
The sets of allowable array "sizes" may be changed in the future and may be large (effectively precluding function overloading)
The function itself should remain fixed regardless of changes to the sets of allowable array "sizes"
The question "Passing a std::array of unknown size to a function", while similar, doesn't appear to directly apply.
The following works in C++14 but seems unnecessarily redundant & messy:
#include <type_traits>
#include <array>
// Add legal/allowable sizes for std::array<> "types" here
// Note: Not married to this; perhaps preprocessor instead?
enum class SizesForArrayX : size_t { Three = 3, Four, Forty = 40 };
enum class SizesForArrayY : size_t { Two = 2, Three, EleventyTwelve = 122 };
// Messy, compile-time, value getter for the above enum classes
template <typename S>
constexpr size_t GetSizeValue(const S size)
{ return static_cast<std::underlying_type_t<S>>(size); }
// An example of the function in question; is Template Argument Deduction
// possible here?
// Note: only arrays of "legal"/"allowable" sizes should be passable
template <SizesForArrayX SX, SizesForArrayY SY>
void PickyArrayHandler(
const std::array<int, GetSizeValue(SX)>& x,
const std::array<int, GetSizeValue(SY)>& y)
{
// Do whatever
for (auto& i : x) i = 42;
for (auto& i : y) while (i --> -41) i = i;
}
Calling the above:
int main()
{
// Declare & (value-)initialize some arrays
std::array<int, GetSizeValue(SizesForArrayX::Forty)> x{};
std::array<int, GetSizeValue(SizesForArrayY::Two>) y{};
//PickyArrayHandler(x, y); // <- Doesn't work; C2672, C2783
// This works & handles arrays of any "allowable" size but the required
// template params are repetitions of the array declarations; ick
PickyArrayHandler<SizesForArrayX::Forty, SizesForArrayY::Two>(x, y);
}
...which is ugly, inelegant, slow-to-compile, and requires the declared array size match the explicit "size" passed to the PickyArrayHandler function template.
For the specific example above: Is there a way for the PickyArrayHandler template to deduce the sizes of the passed arrays?
Generally speaking: Is there a different, better approach?
Since you don't seem to be picky about how the valid sizes are defined, you can use type traits
#include <array>
template <size_t N> struct valid_size1 { enum { value = false }; };
template <size_t N> struct valid_size2 { enum { value = false }; };
template <> struct valid_size1<3> { enum { value = true }; };
template <> struct valid_size1<4> { enum { value = true }; };
template <> struct valid_size1<40> { enum { value = true }; };
template <> struct valid_size2<2> { enum { value = true }; };
template <> struct valid_size2<122> { enum { value = true }; };
template <size_t TX, size_t TY>
void PickyArrayHandler(const std::array<int, TX> &x,
const std::array<int, TY> &y)
{
static_assert(valid_size1<TX>::value, "Size 1 is invalid");
static_assert(valid_size2<TY>::value, "Size 2 is invalid");
// Do whatever
}
int main()
{
// Declare & (value-)initialize some arrays
std::array<int, 40> x{};
std::array<int, 2> y{};
PickyArrayHandler(x, y);
PickyArrayHandler(std::array<int, 4>{}, std::array<int, 2>{});
// PickyArrayHandler(std::array<int, 1>{}, std::array<int, 5>{}); // BOOM!
}
Here's a solution using an array:
#include <iostream>
#include <array>
constexpr size_t valid_1[] = { 3, 4, 40 };
constexpr size_t valid_2[] = { 2, 122 };
template <size_t V, size_t I=0>
struct is_valid1 { static constexpr bool value = V==valid_1[I] || is_valid1<V,I+1>::value; };
template <size_t V, size_t I=0>
struct is_valid2 { static constexpr bool value = V==valid_2[I] || is_valid2<V,I+1>::value; };
template <size_t V>
struct is_valid1<V, sizeof(valid_1)/sizeof(valid_1[0])>
{static constexpr bool value = false; };
template <size_t V>
struct is_valid2<V, sizeof(valid_2)/sizeof(valid_2[0])>
{static constexpr bool value = false; };
template <size_t TX, size_t TY>
void PickyArrayHandler(const std::array<int, TX> &x,
const std::array<int, TY> &y)
{
static_assert(is_valid1<TX>::value, "Size 1 is invalid");
static_assert(is_valid2<TY>::value, "Size 2 is invalid");
// Do whatever
}
twiddled around a bit and got this reduced one working: maybe it helps:
enum SizesForArrayX : size_t { Three = 3, Four, Forty = 40 };
enum SizesForArrayY : size_t { Two = 2, EleventyTwelve = 122 };
template <size_t TX, size_t TY>
void PickyArrayHandler(
const std::array<int, TX>& x,
const std::array<int, TY>& y)
{
// Do whatever
}
int main()
{
// Declare & (value-)initialize some arrays
std::array<int, SizesForArrayX::Forty> x{};
std::array<int, SizesForArrayY::Two> y{};
PickyArrayHandler(x, y);
return 0;
}
Unfortunately, your enums are not continuous so you cannot simply iterate over the enum and you have to handle all cases individually. Since the sizes are known at compile-time you can static_assert for it.
#include <array>
enum SizesForArrayX : size_t { Three = 3, Four, Forty = 40 };
enum SizesForArrayY : size_t { Two = 2, EleventyTwelve = 122 };
template <size_t TX, size_t TY>
void PickyArrayHandler(const std::array<int, TX> &x,
const std::array<int, TY> &y)
{
static_assert(TX == Three || TX == Four || TX == Forty,
"Size mismatch for x");
static_assert(TY == Two || TY == EleventyTwelve, "Size mismatch for y");
// Do whatever
}
int main()
{
// Declare & (value-)initialize some arrays
std::array<int, SizesForArrayX::Forty> x{};
std::array<int, SizesForArrayY::Two> y{};
PickyArrayHandler(x, y);
PickyArrayHandler(std::array<int, 4>{}, std::array<int, 2>{});
//PickyArrayHandler(std::array<int, 1>{}, std::array<int, 5>{}); // BOOM!
}
The best way I see to solve this problem is writing a custom type trait:
template <std::underlying_type_t<SizesForArrayX> SX>
struct is_size_x {
static constexpr bool value = false;
};
template <>
struct is_size_x<static_cast<std::underlying_type_t<SizesForArrayX>>(SizesForArrayX::Forty)>{
static constexpr bool value = true;
};
I'd put these right under the enum class declarations, just so it's easy to check that you got them all. Somebody more clever than I could probably figure out a way to even do this with variadic templates so you only need one specialization.
While tedious, if you have a small set of values this should be fast enough and easy to put in unit tests. The other nice thing about this approach is that if you have multiple functions that need one of these special sizes, you don't have to copy/paste static_asserts around.
With the type traits, your function becomes trivial:
template <std::size_t SX, std::size_t SY>
void PickyArrayHandler(
std::array<int, SX>& x,
std::array<int, SY>& y)
{
static_assert(is_size_x<SX>::value, "Invalid size SX");
static_assert(is_size_y<SY>::value, "Invalid size SY");
// Do whatever
for (auto& i : x) i = 42;
for (auto& i : y) while (i --> -41) i = i;
}
Lastly, you can make a type alias to avoid creating invalid arrays in the first place:
template <typename T, SizesForArrayX SIZE>
using XArray =
std::array<T, static_cast<std::underlying_type_t<SizesForArrayX>>(SIZE)>;
template <typename T, SizesForArrayY SIZE>
using YArray =
std::array<T, static_cast<std::underlying_type_t<SizesForArrayY>>(SIZE)>;
That'll prevent you from declaring an array if it's not an approved size:
XArray<int, SizesForArrayX::Forty> x{};
YArray<int, SizesForArrayY::Two> y{};
Personally I would just manually type the allowable sizes into a static_assert inside PickyArrayHandler. If that's not an option because the sizes will be used in other parts of your program and you're adhering to the DRY principal then I'd use the preprocessor.
#define FOREACH_ALLOWABLE_X(SEP_MACRO) \
SEP_MACRO(3) \
SEP_MACRO(4) \
SEP_MACRO(40) \
#define FOREACH_ALLOWABLE_Y(SEP_MACRO) \
SEP_MACRO(2) \
SEP_MACRO(3) \
SEP_MACRO(122) \
#define COMMA_SEP(NUM) NUM,
#define LOGIC_OR_SEP_X(NUM) N1 == NUM ||
#define LOGIC_OR_SEP_Y(NUM) N2 == NUM ||
#define END_LOGIC_OR false
// some arrays with your sizes incase you want to do runtime checking
namespace allowable_sizes
{
size_t x[] {FOREACH_ALLOWABLE_X(COMMA_SEP)};
size_t y[] {FOREACH_ALLOWABLE_Y(COMMA_SEP)};
}
template <size_t N1, size_t N2>
void PickyArrayHandler(const std::array<int, N1>& x, const std::array<int, N2>& y)
{
static_assert(FOREACH_ALLOWABLE_X(LOGIC_OR_SEP_X) END_LOGIC_OR);
static_assert(FOREACH_ALLOWABLE_Y(LOGIC_OR_SEP_Y) END_LOGIC_OR);
// do whatever
}
#undef FOREACH_ALLOWABLE_X
#undef FOREACH_ALLOWABLE_Y
#undef COMMA_SEP
#undef LOGIC_OR_SEP_X
#undef LOGIC_OR_SEP_Y
#undef END_LOGIC_OR
Some C++ purists will frown at it but it gets the job done.
You could have a is_of_size-like template that check the size of the array, and then use it to disable the template if one of the sizes does not match, something like:
#include <array>
#include <type_traits>
// Forward template declaration without definition.
template <class T, T N, T... Sizes>
struct is_one_of;
// Specialization when there is a single value: Ends of the recursion,
// the size was not found, so we inherit from std::false_type.
template <class T, T N>
struct is_one_of<T, N>: public std::false_type {};
// Generic case definition: We inherit from std::integral_constant<bool, X>, where X
// is true if N == Size or if N is in Sizes... (via recursion).
template <class T, T N, T Size, T... Sizes>
struct is_one_of<T, N, Size, Sizes... >:
public std::integral_constant<
bool, N == Size || is_one_of<T, N, Sizes... >::value> {};
// Alias variable template, for simpler usage.
template <class T, T N, T... Sizes>
constexpr bool is_one_of_v = is_one_of<T, N, Sizes... >::value;
template <std::size_t N1, std::size_t N2,
std::enable_if_t<
(is_one_of_v<std::size_t, N1, 3, 4, 40>
&& is_one_of_v<std::size_t, N2, 2, 3, 122>), int> = 0>
void PickyArrayHandler(
const std::array<int, N1>& x,
const std::array<int, N2>& y)
{
}
Then you can simply:
PickyArrayHandler(std::array<int, 3>{}, std::array<int, 122>{}); // OK
PickyArrayHandler(std::array<int, 2>{}, std::array<int, 3>{}); // NOK
In C++17, you could (I think) replace is_one_of with:
template <auto N, auto... Sizes>
struct is_one_of;
...and automatically deduce std::size_t.
In C++20, you could use a concept to have clearer error messages ;)
Using static_assert for invalid sizes is not a good solution because it doesn't play well with SFINAE; i.e., TMP facilities like std::is_invocable and the detection idiom will return false positives for calls that in fact always yield an error. Far better is to use SFINAE to remove invalid sizes from the overload set, resulting in something resembling the following:
template<std::size_t SX, std::size_t SY,
typename = std::enable_if_t<IsValidArrayXSize<SX>{} && IsValidArrayYSize<SY>{}>>
void PickyArrayHandler(std::array<int, SX> const& x, std::array<int, SY> const& y) {
// Do whatever
}
First we need to declare our valid sizes; I don't see any benefit to stronger typing here, so for a compile-time list of integers, std::integer_sequence works just fine and is very lightweight:
using SizesForArrayX = std::index_sequence<3, 4, 40>;
using SizesForArrayY = std::index_sequence<2, 3, 122>;
Now for the IsValidArraySize traits... The straightforward route is to make use of C++14's relaxed-constexpr rules and perform a simple linear search:
#include <initializer_list>
namespace detail {
template<std::size_t... VSs>
constexpr bool idx_seq_contains(std::index_sequence<VSs...>, std::size_t const s) {
for (auto const vs : {VSs...}) {
if (vs == s) {
return true;
}
}
return false;
}
} // namespace detail
template<std::size_t S>
using IsValidArrayXSize
= std::integral_constant<bool, detail::idx_seq_contains(SizesForArrayX{}, S)>;
template<std::size_t S>
using IsValidArrayYSize
= std::integral_constant<bool, detail::idx_seq_contains(SizesForArrayY{}, S)>;
Online Demo
However if compile times are at all a concern, I suspect the following will be better, if potentially less clear:
namespace detail {
template<bool... Bs>
using bool_sequence = std::integer_sequence<bool, Bs...>;
template<typename, std::size_t>
struct idx_seq_contains;
template<std::size_t... VSs, std::size_t S>
struct idx_seq_contains<std::index_sequence<VSs...>, S>
: std::integral_constant<bool, !std::is_same<bool_sequence<(VSs == S)...>,
bool_sequence<(VSs, false)...>>{}>
{ };
} // namespace detail
template<std::size_t S>
using IsValidArrayXSize = detail::idx_seq_contains<SizesForArrayX, S>;
template<std::size_t S>
using IsValidArrayYSize = detail::idx_seq_contains<SizesForArrayY, S>;
Online Demo
Whichever implementation route is chosen, using SFINAE in this way enables very nice error messages – e.g. for PickyArrayHandler(std::array<int, 5>{}, std::array<int, 3>{});, current Clang 7.0 ToT yields the following, telling you which array's size is invalid:
error: no matching function for call to 'PickyArrayHandler'
PickyArrayHandler(std::array<int, 5>{}, std::array<int, 3>{});
^~~~~~~~~~~~~~~~~
note: candidate template ignored: requirement 'IsValidArrayXSize<5UL>{}' was not satisfied [with SX = 5, SY = 3]
void PickyArrayHandler(std::array<int, SX> const& x, std::array<int, SY> const& y) {
^

Initializing a const vector of pointers to array at compile time using templates

The following class will not compile under C++11; the loop as it stands can only be executed at runtime and so one gets a "char(*)[i] is a variably-modified type" error from the template class static function call within the loop:
#include <cstddef>
#include <vector>
template <std::size_t N>
class Foo
{
private:
const std::vector<char(*)[]> bar = bar_init();
static std::vector<char(*)[]> bar_init()
{
std::vector<char(*)[]> init;
for (size_t i = N; i > 0; i >>= 1)
{
auto ptr_to_array = MyClass<char(*)[i]>::static_return_ptr_to_array();
init.emplace_back(reinterpret_cast<char(*)[]>(ptr_to_array));
}
return init;
}
};
Is there a way I can accomplish the same effect using templates within the initialization function? That is to say, initialize "bar" of size log2(N) at "Foo" class instantiation as a const vector of pointers to array of char with each vector element containing e.g. for N=32 the output of:
MyClass<char(*)[32]>::static_return_ptr_to_array();
MyClass<char(*)[16]>::static_return_ptr_to_array();
MyClass<char(*)[8]>::static_return_ptr_to_array();
//etc...
something like ( in c++11 )
template<int I>
struct tag{};
void init( std::vector<char(*)[]>& result, tag<0> ){}
template<int I>
void init( std::vector<char(*)[]>& result, tag<I> )
{
auto ptr_to_array = MyClass<char(*)[I]>::static_return_ptr_to_array;
result.emplace_back(reinterpret_cast<char(*)[]>(ptr_to_array));
init(result,tag<(I>>1)>{});
}
template <std::size_t N>
class Foo
{
private:
const std::vector<char(*)[]> bar = bar_init();
static std::vector<char(*)[]> bar_init()
{
std::vector<char(*)[]> result;
init( result, tag<N>{} );
return result;
}
};
in c++17 this can be further simplified with an if constexpr and no tag<>. Moreover, note that std::vector<char(*)[]> is not portable because vector needs a complete type.
I think the crucial insight here, is that you can't write:
int i = ?? // automatic variable
auto val = MyClass<char(*)[i]>::static_return_ptr_to_array()
... template arguments have to be constants.
What you can do is something like:
const std::unordered_map<int,???> allocator_map = {
{1, MyClass<char(*)[1]>::static_return_ptr_to_array},
{2, MyClass<char(*)[2]>::static_return_ptr_to_array},
{4, MyClass<char(*)[4]>::static_return_ptr_to_array},
{8, MyClass<char(*)[8]>::static_return_ptr_to_array},
...
};
and then
const auto it = allocator_map.find(i);
if (it == allocator_map.end())
// throw error
auto val = (it->second)();
Basically, the idea is that you have a static array of allocator functions, and then index into it. (There may be clever ways of using templates to initialize the map. I probably would just write it out by hand though - possibly using a preprocessor macro).
If you define index container type traits (or you use std::index_sequence, unfortunately available only starting from C++14)
template <std::size_t ...>
struct indexList
{ };
and you define a type traits to extract a sequence of decreasing power of two
template <std::size_t, typename>
struct iLH;
template <std::size_t N, std::size_t ... Is>
struct iLH<N, indexList<Is...>> : public iLH<(N >> 1), indexList<Is..., N>>
{ };
template <std::size_t ... Is>
struct iLH<0U, indexList<Is...>>
{ using type = indexList<Is...>; };
template <std::size_t N>
struct getIndexList : public iLH<N, indexList<>>
{ };
template <std::size_t N>
using getIndexList_t = typename getIndexList<N>::type;
should be possible write your Foo simply as
template <std::size_t N>
class Foo
{
private:
const std::vector<char **> bar = bar_init (getIndexList_t<N>{});
template <std::size_t ... Is>
static std::vector<char **> bar_init (indexList<Is...> const &)
{
std::vector<char **> init { MyClass<char(*)[Is]>::getPtr()... };
return init;
}
};
(supposing a static getPtr() method in MyClass) that return a char ** and a bar vector of char **).
The following is a full compiling example
template <typename T>
struct MyClass;
template <std::size_t Dim>
struct MyClass<char(*)[Dim]>
{
static char ** getPtr ()
{ static char ach[Dim]; static char * ret { ach } ; return &ret; }
};
template <std::size_t ...>
struct indexList
{ };
template <std::size_t, typename>
struct iLH;
template <std::size_t N, std::size_t ... Is>
struct iLH<N, indexList<Is...>> : public iLH<(N >> 1), indexList<Is..., N>>
{ };
template <std::size_t ... Is>
struct iLH<0U, indexList<Is...>>
{ using type = indexList<Is...>; };
template <std::size_t N>
struct getIndexList : public iLH<N, indexList<>>
{ };
template <std::size_t N>
using getIndexList_t = typename getIndexList<N>::type;
template <std::size_t N>
class Foo
{
private:
const std::vector<char **> bar = bar_init (getIndexList_t<N>{});
template <std::size_t ... Is>
static std::vector<char **> bar_init (indexList<Is...> const &)
{
std::vector<char **> init { MyClass<char(*)[Is]>::getPtr()... };
return init;
}
};
int main ()
{
Foo<32U> f32;
}

Implementing std::array-like constructors in other classes

In all the modern C++ compilers I've worked with, the following is legal:
std::array<float, 4> a = {1, 2, 3, 4};
I'm trying to make my own class that has similar construction semantics, but I'm running into an annoying problem. Consider the following attempt:
#include <array>
#include <cstddef>
template<std::size_t n>
class float_vec
{
private:
std::array<float, n> underlying_array;
public:
template<typename... Types>
float_vec(Types... args)
: underlying_array{{args...}}
{
}
};
int main()
{
float_vec<4> v = {1, 2, 3, 4}; // error here
}
When using int literals like above, the compiler complains it can't implicitly convert int to float. I think it works in the std::array example, though, because the values given are compile-time constants known to be within the domain of float. Here, on the other hand, the variadic template uses int for the parameter types and the conversion happens within the constructor's initializer list where the values aren't known at compile-time.
I don't want to do an explicit cast in the constructor since that would then allow for all numeric values even if they can't be represented by float.
The only way I can think of to get what I want is to somehow have a variable number of parameters, but of a specific type (in this case, I'd want float). I'm aware of std::initializer_list, but I'd like to be able to enforce the number of parameters at compile time as well.
Any ideas? Is what I want even possible with C++11? Anything new proposed for C++14 that will solve this?
A little trick is to use constructor inheritance. Just make your class derive from another class which has a pack of the parameters you want.
template <class T, std::size_t N, class Seq = repeat_types<N, T>>
struct _array_impl;
template <class T, std::size_t N, class... Seq>
struct _array_impl<T, N, type_sequence<Seq...>>
{
_array_impl(Seq... elements) : _data{elements...} {}
const T& operator[](std::size_t i) const { return _data[i]; }
T _data[N];
};
template <class T, std::size_t N>
struct array : _array_impl<T, N>
{
using _array_impl<T, N>::_array_impl;
};
int main() {
array<float, 4> a {1, 2, 3, 4};
for (int i = 0; i < 4; i++)
std::cout << a[i] << std::endl;
return 0;
}
Here is a sample implementation of the repeat_types utility. This sample uses logarithmic template recursion, which is a little less intuitive to implement than with linear recursion.
template <class... T>
struct type_sequence
{
static constexpr inline std::size_t size() noexcept { return sizeof...(T); }
};
template <class, class>
struct _concatenate_sequences_impl;
template <class... T, class... U>
struct _concatenate_sequences_impl<type_sequence<T...>, type_sequence<U...>>
{ using type = type_sequence<T..., U...>; };
template <class T, class U>
using concatenate_sequences = typename _concatenate_sequences_impl<T, U>::type;
template <std::size_t N, class T>
struct _repeat_sequence_impl
{ using type = concatenate_sequences<
typename _repeat_sequence_impl<N/2, T>::type,
typename _repeat_sequence_impl<N - N/2, T>::type>; };
template <class T>
struct _repeat_sequence_impl<1, T>
{ using type = T; };
template <class... T>
struct _repeat_sequence_impl<0, type_sequence<T...>>
{ using type = type_sequence<>; };
template <std::size_t N, class... T>
using repeat_types = typename _repeat_sequence_impl<N, type_sequence<T...>>::type;
First of what you are seeing is the default aggregate initialization. It has been around since the earliest K&R C. If your type is an aggregate, it supports aggregate initialization already. Also, your example will most likely compile, but the correct way to initialize it is std::array<int, 3> x ={{1, 2, 3}}; (note the double braces).
What has been added in C++11 is the initializer_list construct which requires a bit of compiler magic to be implemented.
So, what you can do now is add constructors and assignment operators that accept a value of std::initializer_list and this will offer the same syntax for your type.
Example:
#include <initializer_list>
struct X {
X(std::initializer_list<int>) {
// do stuff
}
};
int main()
{
X x = {1, 2, 3};
return 0;
}
Why does your current approach not work? Because in C++11 std::initializer_list::size is not a constexpr or part of the initializer_list type. You cannot use it as a template parameter.
A few possible hacks: make your type an aggregate.
#include <array>
template<std::size_t N>
struct X {
std::array<int, N> x;
};
int main()
{
X<3> x = {{{1, 2, 3}}}; // triple braces required
return 0;
}
Provide a make_* function to deduce the number of arguments:
#include <array>
template<std::size_t N>
struct X {
std::array<int, N> x;
};
template<typename... T>
auto make_X(T... args) -> X<sizeof...(T)>
// might want to find the common_type of the argument pack as well
{ return X<sizeof...(T)>{{{args...}}}; }
int main()
{
auto x = make_X(1, 2, 3);
return 0;
}
If you use several braces to initialize the instance, you can leverage list-init of another type to accept these conversions for compile-time constants. Here's a version that uses a raw array, so you only need parens + braces for construction:
#include <array>
#include <cstddef>
template<int... Is> struct seq {};
template<int N, int... Is> struct gen_seq : gen_seq<N-1, N-1, Is...> {};
template<int... Is> struct gen_seq<0, Is...> : seq<Is...> {};
template<std::size_t n>
class float_vec
{
private:
std::array<float, n> underlying_array;
template<int... Is>
constexpr float_vec(float const(&arr)[n], seq<Is...>)
: underlying_array{{arr[Is]...}}
{}
public:
constexpr float_vec(float const(&arr)[n])
: float_vec(arr, gen_seq<n>{})
{}
};
int main()
{
float_vec<4> v0 ({1, 2, 3, 4}); // fine
float_vec<4> v1 {{1, 2, 3, 4}}; // fine
float_vec<4> v2 = {{1, 2, 3, 4}}; // fine
}
Explicitly specify that the data type for the initialization to floating point type. You can do this by doing "1.0f" instead of putting "1". If it is a double, put "1.0d". If it is a long, put "1l" and for unsigned long put "1ul" and so on..
I've tested it here: http://coliru.stacked-crooked.com/a/396f5d418cbd3f14
and here: http://ideone.com/ZLiMhg
Your code was fine. You just initialized the class a bit incorrect.
#include <array>
#include <cstddef>
#include <iostream>
template<std::size_t n>
class float_vec
{
private:
std::array<float, n> underlying_array;
public:
template<typename... Types>
float_vec(Types... args)
: underlying_array{{args...}}
{
}
float get(int index) {return underlying_array[index];}
};
int main()
{
float_vec<4> v = {1.5f, 2.0f, 3.0f, 4.5f}; //works fine now..
for (int i = 0; i < 4; ++i)
std::cout<<v.get(i)<<" ";
}