Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initializing private std::array member in the constructor

I was wondering what is the proper way to initialize a std::array member of the class in the constructor, when the initial array values are parameters to the constructor?

More specifically, consider the following example:

class Car {
  public:
    Car(const std::string& color, int age): color_(color), age_(age) {}
    // ...
  private:
    std::string color_;
    int age_;
};

class ThreeIdenticalCars {
  private:
    std::array<Car, 3> list;
  public:
    ThreeIdenticalCars(const std::string& color, int age):
    // What to put here to initialize list to 3 identical Car(color,age) objects?
   {}
};

Obviously one way is to write list({Car(color,age), Car(color,age), Car(color,age)}), but this clearly does not scale if we wanted 30 identical cars instead of three.

If instead of std::array I used std::vector the solution would have been list(3, Car(color,age) (or list(30, Car(color, age)) but as in my problem the size of the list is known, I thought it is more correct to use std:array.

like image 316
MikeL Avatar asked Jun 07 '18 12:06

MikeL


2 Answers

One option for the array version is to use a template function to build the array. You'll have to test to see if this gets optimized out or copied in release mode,

   #include <iostream>
#include <array>
#include <tuple>

class Car {
    public:
    Car(const std::string& color, int age): color_(color), age_(age) {}
    // ...
    //private:
    std::string color_;
    int age_;
};

template <typename CarType, typename... Args ,size_t... Is>
std::array<CarType,sizeof...(Is)> make_cars(std::index_sequence<Is...>,Args&&... args )
{
    return { (Is,CarType(args...))... };
}

class ThreeIdenticalCars {
    //private:
    public:
    std::array<Car, 3> list;
  //public:
    ThreeIdenticalCars(const std::string& color, int age) : 
        list(make_cars<decltype(list)::value_type>(
            std::make_index_sequence<std::tuple_size<decltype(list)>::value>(),
            color,
            age
            ))
   {}
};

int main()
{
    ThreeIdenticalCars threecars("red", 10);

    for(auto& car : threecars.list)
        std::cout << car.color_ << " " << car.age_ << std::endl;

    return 0;
}

Demo

like image 197
rmawatson Avatar answered Oct 15 '22 08:10

rmawatson


Great answer by rmawatson.

Here's a similar alternative which attempts 2 enhancements:

  1. Construction by model.
  2. copy the model N-1 times and move the last one into place.

Of course it requires that a Car is copy-constructible.

#include <array>
#include <string>

class Car {
  public:
    Car(const std::string& color, int age): color_(color), age_(age) {}
    // ...
  private:
    std::string color_;
    int age_;
};

namespace detail
{
    template<std::size_t...Is, class Model>
    auto build_array_impl(std::index_sequence<Is...>, Model&& model)
    {
        constexpr auto size = sizeof...(Is) + 1;
        return std::array<std::decay_t<Model>, size>
        {
            // N-1 copies
            (Is, model)...,

            // followed by perfect forwarding for the last one
            std::forward<Model>(model)
        };
    }
}

template<std::size_t N, class Type>
auto build_array(std::integral_constant<std::size_t, N>, Type&& model)
{
    return detail::build_array_impl(std::make_index_sequence<N-1>(), 
                                    std::forward<Type>(model));
}

class ThreeIdenticalCars {
  private:
    static constexpr auto num_cars = std::size_t(3);
    static constexpr auto num_cars_c = std::integral_constant<std::size_t, num_cars>();
    std::array<Car, num_cars> list;
  public:
    ThreeIdenticalCars(const std::string& color, int age)
    : list(build_array(num_cars_c, Car(color, age)))
   {}
};

int main()
{
    ThreeIdenticalCars tic("red", 1);
}
like image 32
Richard Hodges Avatar answered Oct 15 '22 08:10

Richard Hodges