I have a class defining an array of fixed length n
, with some methods.
template<int n>
struct array_container{
/* some code here */
int array[n];
};
Let's say I want to add a constructor to array_container<3>
, something along the lines of:
array_container<3>::array_container(int a0, int a1 ,int a2){
array[0] = a0;
array[1] = a1;
array[2] = a1;
}
I know of two ways to do this:
One is to copy the entire code of the generic class, replacing n
with 3, and add my constructor:
template<>
struct array_container<3>{
/* some code here */
int array[3];
array_container(int a0, int a1 ,int a2){
array[0] = a0;
array[1] = a1;
array[2] = a1; }
};
This works correctly, but has the disadvantage of needing to copy all the code and methods from the generic base.
Another method is to add a constructor array_container(int a0, int a1, int a2);
in the generic class, then define:
template<>
array_container<3>:: array_container(int a0, int a1 ,int a2){
array[0] = a0;
array[1] = a1;
array[2] = a1; }
This has the disadvantage of populating my generic base class with at best undefined or at worst incorrect constructors, such as
array_container<2>(int a0, int a1 ,int a2)
(undefined or incorrect depending on whether or not I add the definition to the generic base or not).
Is there any approach that avoids both pitfalls? Ie. doesn't need to copy-paste the entire generic base code for the specialization, and doesn't add unnecessary constructors to the generic base?
CodePudding user response:
Why not simply use
template <std::size_t size>
struct MyArrayWithFunctions : std::array<int, size> {
/* some functions */
};
std::array
allows aggregate initialization and even deduces the size so you can simply write
MyArrayWithFunctions arr{1,2,3};
You can add a deduction guideline for your own class but why re-implement array
?
CodePudding user response:
The easier solution would be to construct it with an array(in place).
template<int n>
struct array_container{
int array[n];
array_container(std::array<int, n> arrayIn)
{
std::copy(arrayIn.begin(), arrayIn.end(), array);
}
};
Otherwise you can mess with variadic templates and parameter unpacking.
CodePudding user response:
If happen to have a C 17 compiler you can use the following code:
#include <type_traits>
// class definition
template<int n>
struct array_container {
int array[n];
template<typename... Args, typename std::enable_if_t<(std::is_same_v<int, Args> && ...) && (sizeof...(Args) == n), bool> = true>
array_container(Args... args):
array{args...}
{}
};
// user defined template deduction guide
template<typename... Args>
array_container(Args...) -> array_container<sizeof...(Args)>;
and use it like
array_container<3> x {1,2,3};
or even let the size be deduced from the number of arguments like
// n deduced to 3 from the number of arguments
array_container x {1,2,3};
The constructor is a variadic template, taking any number of int
arguments (the latter is enforced by the std::enable_if_t
template parameter) and initializes the array
member from them. A user defined deduction guide can be used to automatically deduce the n
parameter from the number of arguments, that you pass to the constructor.
See it live on godbolt.
CodePudding user response:
Is there any approach that avoids both pitfalls? Ie. doesn't need to copy-paste the entire generic base code for the specialization, and doesn't add unnecessary constructors to the generic base?
Ignoring the fact that, as @Goswin von Brederlow mentions, you seem to be reinventing the wheel (std::array
and aggregate initialization), C 20's requires-expressions allows you to define constructors in a primary template that are constrained to only certain specializations. E.g.:
#include <type_traits>
// Helper trait for constraining a ctor to a set
// of specializations.
template <int n, int... ns> struct is_one_of {
static constexpr bool value{false};
};
template <int n, int n0, int... ns> struct is_one_of<n, n0, ns...> {
static constexpr bool value{(n == n0) || is_one_of<n, ns...>::value};
};
template <int n, int... ns>
inline constexpr bool is_one_of_v{is_one_of<n, ns...>::value};
template <int n> struct array_container {
/* some code here */
int array[n];
// Constrained to n == 3 or n == 5.
template <typename... Args>
requires(std::is_same_v<Args, int> &&...) && (sizeof...(Args) == n) &&
is_one_of_v<n, 3, 5 /*, ... */> array_container(Args... args)
: array{args...} {}
};
// Demo.
array_container<3> arr3{1, 2, 3}; // OK
array_container<4> arr4{1, 2, 3, 4}; // Error (no matching ctor)
array_container<5> arr5{1, 2, 3, 4, 5}; // OK