Home > database >  Adding new constructors to a specialised template class
Adding new constructors to a specialised template class

Time:07-08

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
  • Related