17

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))...}) {}
};  
ony
  • 12,457
  • 1
  • 33
  • 41

2 Answers2

17

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]

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • In my case I want to add implicit cast to one column/row matrix. I'm not sure it is possible to do with external to class declarations. And to put specialized ctor for matrix that accepts array I'll have to factor out "generic_matrix" and then add specializations that inherits from it. Kinda awkward too. – ony Jun 18 '14 at 09:08
  • 4
    @ony: inheriting from a class not designed to be inherited from is awkward too, and frowned upon in general. – Matthieu M. Jun 18 '14 at 09:10
  • @MatthieuM., right now I have `template struct matrix { ... };`. But to achieve what you suggest I have to make 2-3 specializations that will have different set of ctors, but same set of members. `template struct matrix : generic_matrix { matrix(array); }` and `template struct matrix : generic_matrix { using generic_matrix };`. For me it looks awkward work-around to introduce additional ctor from `array` for special cases when either `M == 1` or `N == 1`. – ony Jun 20 '14 at 08:10
  • @ony You could put all constructors in the primary template use [SFINAE](http://en.wikipedia.org/wiki/SFINAE) to enable/disable them as relevant to template arguments. – Angew is no longer proud of SO Jun 20 '14 at 08:18
  • @Angew, something tells me that using `static_assert` will have same effect. Though I'm a big anti-fan of sfinae and I may just miss something. – ony Jun 20 '14 at 08:26
  • 1
    @ony Assuming the constructors are mutually exclusive, `static_assert` will do the job as well (and provide a nicer error message). However, SFINAE can be used to control overload resolution - you can have the same client code call one or another overload using SFIANE, which `static_assert` can't give you. – Angew is no longer proud of SO Jun 20 '14 at 08:29
1

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;
}
  • Instead f writing 3 different ctor's and pass arguments through intermediate `std::array` you can use benefit of `std::forward` as mentioned in the question. But it isn't elegant enough as for me. – ony Jun 11 '17 at 15:42