Pass initializer list to function for initialization of std::array

477 Views Asked by At

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?

2

There are 2 best solutions below

0
Andrej Podzimek On

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.

0
A M On

There is basically no problem to use a std::initializer list for your class. You just need to copy the data manually.

You can also check the number of parameters in the std::initializer_list and act accordingly. In my below example I used assert to check that.

You can also implement the rule of 3 or 5. Although not needed for this simple class. I added it just for demo purposes.

I also recomend to use parameters with default arguments. For this example, it will make life easier.

Maybe best is to show an example:

#include <iostream>
#include <array>
#include <initializer_list>
#include <algorithm>
#include <fstream>
#include <cassert>

enum class types : int { au };

class Point {
    std::array<double, 3> coordinate{};
    types type{};
public:

    // Rule of five. Not needed in this case
    Point() {};
    Point(const Point& other) { coordinate = other.coordinate; type = other.type; }
    Point& operator = (const Point& p) { if (this == &p) return *this; coordinate = p.coordinate; type = p.type; return *this; }
    Point(Point&& other) noexcept { coordinate = std::move(other.coordinate); type = other.type; }
    Point& operator = (Point&& other) noexcept { coordinate = std::move(other.coordinate); type = other.type; return *this; }

    virtual ~Point() {};

    // Special constructors
    Point(const std::initializer_list<double>& il, types t = types::au) : type(t){
        assert((il.size() == 3) && "\nDynamic Assert: Only 3 elements allowed\n");
        std::copy_n(il.begin(), 3, coordinate.begin());
    }
    Point(const double x, const double y, const double z, types t = types::au) : type(t) {
        coordinate[0] = x; coordinate[1] = y; coordinate[2] = z;
    }
    Point(const double(&c)[3], types t = types::au) : type(t) {
        std::copy_n(std::begin(c), 3, coordinate.begin());
    }
    friend std::ostream& operator << (std::ostream& os, const Point& p) {
        return os << p.coordinate[0] << '\t' << p.coordinate[1] << '\t' << p.coordinate[2];
    }
    // Special assignment
    Point& operator = (const std::initializer_list<double>& il) {
        assert((il.size() == 3) && "\nDynamic Assert: Only 3 elements allowed\n");
        std::copy_n(il.begin(), 3, coordinate.begin());
        return *this;
    }
    Point& operator = (const double(&c)[3]) {
        std::copy_n(std::begin(c), 3, coordinate.begin());
        return *this;
    }

};
int main() {
    Point p1({ 1,2,3 });
    std::cout << "p1:\t" << p1 << '\n';

    Point p2({ 4,5,6 }, types::au);
    std::cout << "p2:\t" << p2 << '\n';

    Point p3( 7,8,9 );
    std::cout << "p3:\t" << p3 << '\n';

    Point p4( 10,11,12, types::au);
    std::cout << "p4:\t" << p4 << '\n';

    double d5[3] = { 13,14,15 };
    Point p5(d5);
    std::cout << "p5:\t" << p5 << '\n';

    double d6[3] = { 16,17,18 };
    Point p6(d6, types::au);
    std::cout << "p6:\t" << p6 << '\n';

    Point p7(p6);
    std::cout << "p7:\t" << p7 << '\n';

    Point p8;
    p8 = p7;
    std::cout << "p8:\t" << p8 << '\n';

    Point p9(19, 20, 21);
    Point p10(std::move(p9));
    std::cout << "p10:\t" << p10 << '\n';

    Point p11(22,23,24);
    Point p12{};
    p12 = (std::move(p11));
    std::cout << "p12:\t" << p12 << '\n';

    Point p13{};
    p13 = { 25,26,27 };
    std::cout << "p13:\t" << p13 << '\n';

    Point p14{};
    double d14[3] = { 28,29,30 };

    p14 = d14;
    std::cout << "p14:\t" << p14 << '\n';
}