fill std::array in the member initialization list
Asked Answered
R

5

11

The following code works but I would like to avoid the warning:

warning: 'fitness::vect_' should be initialized in the member initialization list [-Weffc++]

when it is compiled with the g++ -Weffc++ switch:

#include <array>

template<class T, unsigned N>
class fitness
{
public:
  explicit fitness(T v)
  {
    static_assert(N, "fitness zero length");

    vect_.fill(v);
  }

private:
  std::array<T, N> vect_;
};

int main()
{
  fitness<double, 4> f(-1000.0);

  return 0;
}

Should I ignore the warning? Is there a way to fill vect_ in the constructor initialization list (without changing its type)?

Rase answered 24/2, 2014 at 16:47 Comment(1)
It isn't easy to fill it, but you can initialize it as vect_() or vect_{}.Constrictor
A
1

A function that generates a filled_array should have its return value be elided:

template<unsigned N, typename T>
std::array<T, N> filled_array_sized( T const& t ) {
  std::array<T, N> retval;
  retval.fill( t );
  return retval;
}

but that requires passing in at least the size N, if not the type T.

template<typename T>
struct array_filler {
  T && t;
  template<typename U, unsigned N>
  operator std::array<U, N>()&& {
    return filled_array_sized<N, U>( std::forward<T>(t) );
  }
  array_filler( T&& in ):t(std::forward<T>(in)) {}
};
template<typename T>
array_filler< T >
filled_array( T&& t ) {
  return array_filler<T>( t );
}

note that storing the return value of filled_array in an auto is not advised.

Use:

#include <array>

template<class T, unsigned N>
class fitness
{
public:
  explicit fitness(T v): vect_( filled_array( std::move(v) ) ) {
    //...
  }
//...

I do not know if the above code will generate a warning in the implementation of filled_array_size, but if it does, disable the warning locally.

Afoul answered 24/2, 2014 at 18:31 Comment(4)
You could simplify this quite a bit by taking advantage of the fact that fill takes a const T&; it doesn't really add anything to do all that forwarding when the element is going to be copied anyway.Fortunato
@StuartOlsen true, but fill could eventually be improved with an rvalue version that copies the first N and moves into the last one! ... ok ok, that is pretty marginal.Afoul
@StuartOlsen I took advantage of the const&, but because of type incompatibilities between the array and the initializing type (which I cannot know in the filled_array case, as we don't have access to the array-type until later), I have to do forwarding gymnastics until I reach filled_array_sized. Note that the above relies extensively on RVO and NRVO to be efficient.Afoul
Well for my purpose the simple filled_array_sized is good enough (with RVO it's as efficient as the other solutions and seems to be more in the spirit of Effective C++). Anyway the post is very instructive.Rase
H
4

I believe you can ignore this warning.

It works if you place an empty initialization for the array in the constructor:

#include <array>

template<class T, unsigned N>
class fitness
{
public:
  explicit fitness(T v):
  vect_{}
  {
    static_assert(N, "fitness zero length");

    vect_.fill(v);
  }

private:
  std::array<T, N> vect_;
};

int main()
{
  fitness<double, 4> f(-1000.0);

  return 0;
}
Hahnert answered 24/2, 2014 at 16:56 Comment(0)
C
2

Try to use

explicit fitness(T v) : vect_{}
{
//...
}
Catafalque answered 24/2, 2014 at 16:56 Comment(0)
B
2

The default constructor (read: value initializer) should work fine in this case. As std::array is an aggregate type, its elements will be each be value-initialized, which for numeric types like double means zero-initialization, and then you can use fill.

explicit fitness(T v) : vect_() // or vect_{}
{
    vect_.fill(v);
}

You might be better off using std::vector and its fill constructor if you don't want to do essentially double the initialization, though. Then your class would become:

template<class T>
class fitness
{
public:
  explicit fitness(T v, unsigned n) : vect_(n, v)

private:
  std::vector<T> vect_;
};

int main()
{
   fitness<double> f(-1000.0, 4);

   return 0;
}

You could, of course, still keep the N as a template parameter, but there's no need to do so as the length doesn't need to be known at compile-time. (On the other hand, if you stick with std::array you might be able to set up the constructor as a constexpr, though that may require some template fiddling or an auxiliary constexpr function that returns an initializer list to work right, I haven't played around enough with C++11 concepts to know.)

Bresee answered 24/2, 2014 at 16:57 Comment(6)
Initially I used a std::vector but std::array proved to give better performance (fitness class is critical for my framework). I'll check if the compiler is able to avoid a double initialization.Rase
...since std::array is an aggregate, default construction won't zero the memory, but will leave it uninitialised (so no double initialization).Rase
For vector to be noticeably slower than array is rare, if you use it properly.Wheeze
@Rase Going by en.cppreference.com/w/cpp/language/value_initialization, en.cppreference.com/w/cpp/language/aggregate_initialization, using an empty expression list in the constructor will indeed zero out the (applicable) members of an aggregate in C++11; if you use vect_(), it will perform value-initialization on each element of the array, which for doubles will cause them to be zero-initialized, and if you use vect_{}, it will perform aggregate initialization with an empty initializer list, which results in the same per-element value-initialization.Bresee
Of course, if T is a type with any kind of explicit constructor, then the values will be default-initialized instead (which may result in uninitialized members of T if T does not have a default constructor that provides default initializers for its members).Bresee
Of course you are right - sorry. Anyway, luckily, with optimizations turned on the compiler (at least a recent g++/clang++) doesn't output useless code.Rase
A
1

A function that generates a filled_array should have its return value be elided:

template<unsigned N, typename T>
std::array<T, N> filled_array_sized( T const& t ) {
  std::array<T, N> retval;
  retval.fill( t );
  return retval;
}

but that requires passing in at least the size N, if not the type T.

template<typename T>
struct array_filler {
  T && t;
  template<typename U, unsigned N>
  operator std::array<U, N>()&& {
    return filled_array_sized<N, U>( std::forward<T>(t) );
  }
  array_filler( T&& in ):t(std::forward<T>(in)) {}
};
template<typename T>
array_filler< T >
filled_array( T&& t ) {
  return array_filler<T>( t );
}

note that storing the return value of filled_array in an auto is not advised.

Use:

#include <array>

template<class T, unsigned N>
class fitness
{
public:
  explicit fitness(T v): vect_( filled_array( std::move(v) ) ) {
    //...
  }
//...

I do not know if the above code will generate a warning in the implementation of filled_array_size, but if it does, disable the warning locally.

Afoul answered 24/2, 2014 at 18:31 Comment(4)
You could simplify this quite a bit by taking advantage of the fact that fill takes a const T&; it doesn't really add anything to do all that forwarding when the element is going to be copied anyway.Fortunato
@StuartOlsen true, but fill could eventually be improved with an rvalue version that copies the first N and moves into the last one! ... ok ok, that is pretty marginal.Afoul
@StuartOlsen I took advantage of the const&, but because of type incompatibilities between the array and the initializing type (which I cannot know in the filled_array case, as we don't have access to the array-type until later), I have to do forwarding gymnastics until I reach filled_array_sized. Note that the above relies extensively on RVO and NRVO to be efficient.Afoul
Well for my purpose the simple filled_array_sized is good enough (with RVO it's as efficient as the other solutions and seems to be more in the spirit of Effective C++). Anyway the post is very instructive.Rase
P
1

Here's another way, cleaner IMHO, using the C++11 non-static data member initializers:

#include <array>

template<class T, unsigned N>
class fitness
{
public:
  explicit fitness(T v)
  {
    static_assert(N, "fitness zero length");

    vect_.fill(v);
  }

private:
  std::array<T, N> vect_ { };
};

int main()
{
  fitness<double, 4> f(-1000.0);

  return 0;
}
Pudency answered 24/2, 2014 at 21:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.