Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::array constructor inheritance

Tags:

c++

c++11

I'm trying to get extended variant of std::array for math vectors (and expose same interface as array does without boilerplate code). I know about std::valarray but I want fixed size for proper typing in matrix multiplications. Thus I array fits perfectly. But when I try to inherit constructor it fails.

struct vec2d : std::array<float, 2>
{ using array::array; }; // simplified

struct vec : std::vector<float>
{ using vector::vector; };

std::array<float, 2> x = {1, 2};
vec y = {1, 2};
vec2d z = {1, 2}; // error: could not convert ‘{1, 2}’ 
                  //        from ‘<brace-enclosed initializer list>’ to ‘vec2d’

This error reported for GCC 4.8.2 and for clang 3.4. Last says that vec2d have only implicit default/copy/move constructors. Yes, array have only implicit ctor in contrary to vector which have ctor from initializer_list. But since ctors are inherited it is natural to inherit possibility to initialize it in a same way as array initialized.

Question: Why we have that error instead of expected behavior (similar to array initialization)?

Note: I that I can write forwarding manually to make it work, but this doesn't look as elegant as ctor inheritance.

struct vec2d : std::array<float, 2>
{
    using array::array;
    // nasty boilerplate code I don't want to have in C++11
    template <typename... Args>
    vec2d(Args &&... args) : array({float(std::forward<Args>(args))...}) {}
};  
like image 675
ony Avatar asked Jun 18 '14 08:06

ony


2 Answers

std::array is designed to be an aggregate, so it intentionally does not define any constructors.

Unfortunately, this means it's not possible to inherit from it and get the same behaviour, as aggregates cannot have base classes.

Why do you need to inherit from std::array anyway? Do you plan to add any private members? If not, then you could just build your framework around free functions operating on std::array, or perhaps a typedef to it.

If you really want to inherit from std::array, you'll have to accept losing the aggregate status and provide any constructors you want yourself.


Note that the answer above applies to C++11 and C++14 only. In C++17, the definition of aggregates was loosened to allow public base classes in them, so simply deriving from std::array and removing the using declaration is enought to make the code compile:

struct vec2d : std::array<float, 2>
{ }; // simplified

std::array<float, 2> x = {1, 2};
vec2d z = {1, 2};

[Live example]

like image 132
Angew is no longer proud of SO Avatar answered Nov 18 '22 22:11

Angew is no longer proud of SO


I had exactly the same problem, trying to mimic the behavior of numpy. The way I solved this was to implement a constructor that takes as argument an std::array<float,N> (with & or && or without anything depending on the needs). The initializer list is then cast automatically to that type, and the right constructor is then called. To be more concrete:

#include <array>
#include <ostream>
#include <iostream>

using namespace std;

template <int N> class Row: public array<double,N>{
    public:
        Row<N>(){}
        // this is the constructor needed
        Row<N>(array<double,N> a) : array<double,N> (a) {}
        // or, alternatively,
        // Row<N>(array<double,N>&& a) : array<double,N> (a) {}
        Row<N>(array<double,N>& a) : array<double,N> (a) {}

        // other things that may be relevant
        Row<N> operator+(Row<N>& other){
            Row<N> result;
            for(int i =0; i < N ; ++i){
                result[i] = (*this)[i] + other[i]; // notice '*'
            }
            return result;
        }
        // for lvalues
        template <int n> friend ostream& operator<<(ostream& os, Row<n>& r);
        // for rvalues
        template <int n> friend ostream& operator<<(ostream& os,Row<n>&& r);
};

// for lvalues
template <int N> ostream& operator<<(ostream& os, Row<N>& r){
    for(int i =0; i < N ; ++i) os << r[i] << "\t";
    return os;
}

// for rvalues
template <int N> ostream& operator<<(ostream& os, Row<N>&& r){
    for(int i =0; i < N ; ++i) os << r[i] << "\t";
    return os;
}

int main(){
    // here Row(array<double,3>&&) is called
    // or   Row(array<double,3>)
    Row<3> a{{1,2,3}}; // same as Row<3> a({1,2,3});
    array<double,3> arr = {1,2,3};
    Row<3> b(arr);

    cout << a << endl; // a and b are lvalues
    cout << b << endl;

    cout << (a+b) << endl; // (a+b) is a rvalue

    return 0;
}
like image 38
Michele Mesiti Avatar answered Nov 18 '22 23:11

Michele Mesiti