Home > front end >  Pass initializer list to function for initialization of std::array
Pass initializer list to function for initialization of std::array

Time:01-01

In my Point header I have:

 15 template<typename real> class Point
 16 {
 17 public:
 18     // Constructors
 19     Point();
 20     Point(const std::initializer_list<real>&);
 21     Point(const std::initializer_list<real>&, const types::unitTypes);
 22     Point(const real, const real, const real);
 23     Point(const real, const real, const real, const types::unitTypes);
...
 43 private:
 44     std::array<real, 3> xyz_;
 45     types::unitTypes units_;
 46 };

Note that lines 20 and 44 show that the Point to should be able to be initialized with an initializer_list and I have private variable std::array<real, 3> xyz_. Now, I would like for my constructor of this to be something like the following:

 31 template<typename T>
 32 Point<T>::Point(const std::initializer_list<T>& xyz)
 33     : xyz_(xyz)
 34     , units_(types::au)
 35 {};

However, it doesn't seem like I'm able to construct the array from an initializer and if I try to move that modify that from :xyz_(xyz) to something like

 31 template<typename T>
 32 Point<T>::Point(std::initializer_list<T> xyz)
 33     : units_(types::au)
 34 {
 35     xyz_ = xyz;
 36 };

it is not able to overload = for operands array and initializer. Is there a better way to go about this that I can use invoke Point<real>({x, y, z}); and initialize the xyz_ array internally?


Update:

I had tried to define Point(const std::array<real, 3>&) before but I get the following compilation error (essential parts extracted):

error: call of overloaded ‘Point(<brace-enclosed initializer list>)’ is ambiguous
...
note: candidate: ‘aided::point::Point<real>::Point(const std::array<real, 3>&) [with real = float]’
...
note: candidate: ‘constexpr aided::point::Point<float>::Point(const aided::point::Point<float>&)’
...
note: candidate: ‘constexpr aided::point::Point<float>::Point(aided::point::Point<float>&&)’

The first is candidate is the one I am intending to use. Are the second two somehow copy constructors that are able to be invoked via an initialization with an initializer list?

CodePudding user response:

std::initializer_list and std::array don’t cooperate as well as one would hope. Separate constructor arguments can give you a bit more flexibility, automatic template argument deduction and (also, to some extent) automatic choice of a type that can hold all the values:

#include <array>
#include <iostream>
#include <type_traits>
#include <utility>

template <typename Real, size_t N>
struct Point {
  template <typename... R>
  Point(R &&...reals) : xyz_{static_cast<Real>(std::forward<R>(reals))...} {}

 private:
  std::array<Real, N> xyz_;

  template <size_t Head, size_t... Tail>
  void print(std::ostream &out, std::index_sequence<Head, Tail...>) const {
    out << xyz_[Head];
    ((out << ',' << xyz_[Tail]), ...);
  }

  friend std::ostream &operator<<(std::ostream &out, const Point &point) {
    out << typeid(Real).name() << ' ' << '[';
    point.print(out, std::make_index_sequence<N>());
    return out << ']';
  }
};

template <typename... R>
Point(R &&...) -> Point<std::common_type_t<R...>, sizeof...(R)>;

Now let’s test that↑ a bit and let’s not insist on Real too strongly:

#include <complex>
#include "that_magic_point.h"

int main() {
  Point p0{1, 2, 3};                  // int
  Point p1{4., 5., 6.};               // double
  Point p2{7, 8., 9};                 // int, double -> double
  Point p3{10, 11, 12ll};             // int, long long -> long long
  Point p4{1};                        // int
  Point p5{2., 3.};                   // double
  Point p6{4, 5., 6u, 7};             // int, double, unsigned -> double
  Point p7{std::complex{1, 2}, 3};    // complex<int>, int -> complex<int>
  Point p8{4, std::complex{5., 6.}};  // int, complex<double> -> complex<double>

  // Caveat: This resolves (incorrectly) to complex<int>:
  // Point p9{std::complex{7, 8}, 9.};

  // Caveat: This will not compile (cast from complex<int> to complex<double>):
  // Point<std::complex<double>, 2> p9{std::complex{7, 8}, 9.};

  // Caveat: This is verbose:
  Point<std::complex<double>, 2> p9{std::complex<double>{7, 8}, 9.};

  std::cout << p0 << '\n'
            << p1 << '\n'
            << p2 << '\n'
            << p3 << '\n'
            << p4 << '\n'
            << p5 << '\n'
            << p6 << '\n'
            << p7 << '\n'
            << p8 << '\n'
            << p9 << '\n';
}

This↑ seems to work and may generate the following output (modulo compilers’ RTTI naming differences):

i [1,2,3]
d [4,5,6]
d [7,8,9]
x [10,11,12]
i [1]
d [2,3]
d [4,5,6,7]
St7complexIiE [(1,2),(3,0)]
St7complexIdE [(4,0),(5,6)]
St7complexIdE [(7,8),(9,0)]

Solving some of the caveats outlined in comments using deep(er) template decomposition and specialization would be a nice exercise, but may not be worth the hassle.

  • Related