std::array 构造函数继承

17

我想要一个扩展的std::array来表示数学向量(并且提供与array相同的接口,无需样板代码)。我知道std::valarray,但是我需要固定大小以便在矩阵乘法中进行正确类型转换。因此,array非常适合。但是当我尝试继承构造函数时会失败。

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’

这个错误报告适用于GCC 4.8.2和clang 3.4。后者表示vec2d只有隐式默认/复制/移动构造函数。是的,array只有与vector相反具有隐式构造函数,不从initializer_list构造。但由于构造函数是继承的,继承了用与array初始化的同样的方式进行初始化是很自然的。 问题:为什么我们会遇到这个错误而不是期望的行为(类似于array初始化)? 注意:我可以手动编写转发来使其正常工作,但这看起来不如构造函数继承优雅。
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))...}) {}
};  
2个回答

17

std::array被设计成一个聚合体,因此它故意不定义任何构造函数。

不幸的是,这意味着无法从中继承并获得相同的行为,因为聚合体不能有基类。

无论如何,为什么需要继承std::array呢?您计划添加任何私有成员吗?如果没有,那么您可以围绕在std::array上操作的自由函数或者可能是一个typedef来构建您的框架。

如果您真的想从std::array继承,就必须接受失去聚合体状态并自己提供任何想要的构造函数。


请注意,上面的答案仅适用于C++11和C++14。在C++17中,聚合体的定义放宽以允许其中有公共基类,因此简单地派生自std::array并删除using声明就足以使代码编译:

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

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

[在线示例]


在我的情况下,我想将隐式转换添加到一个列/行矩阵中。我不确定是否可以通过类外声明来实现。如果要为接受数组的矩阵放置专门的构造函数,我必须将“generic_matrix”分解出来,然后添加继承自它的特殊化。有点尴尬。 - ony
4
继承自不适合被继承的类也很尴尬,通常会受到反对。 - Matthieu M.
@MatthieuM.,现在我有template<typename T, size_t N, size_t M> struct matrix { ... };。但是为了实现您建议的内容,我必须进行2-3个特化,这些特化将具有不同的构造函数集,但是相同的成员集。template<typename T, size_t N> struct matrix<T, N, 1> : generic_matrix<T, N, 1> { matrix(array<T, N>); }template<typename T, size_t N, size_t M> struct matrix<T, N, M> : generic_matrix<T, N, M> { using generic_matrix };。对我来说,引入额外的从array到特殊情况的构造函数似乎是一种笨拙的解决方法,当M == 1N == 1时使用。 - ony
@ony 您可以将所有构造函数放在主模板中,使用SFINAE根据模板参数启用/禁用它们。 - Angew is no longer proud of SO
@Angew,有些东西告诉我使用static_assert会产生相同的效果。虽然我非常反感sfinae,但我可能会错过一些东西。 - ony
1
假设构造函数是互斥的,static_assert同样可以完成任务(并提供更好的错误消息)。然而,SFINAE可用于控制重载解析 - 您可以使用SFIANE使相同的客户端代码调用一个或另一个重载,而static_assert无法提供此功能。 - Angew is no longer proud of SO

1
我曾经遇到过完全相同的问题,试图模仿numpy的行为。 我解决这个问题的方法是实现一个构造函数,该函数以std::array<float,N>作为参数(根据需要使用&&&或不带任何内容)。 然后初始化列表会自动转换为该类型,并调用正确的构造函数。具体来说:
#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;
}

不必编写3个不同的ctor并通过中间 std::array 传递参数,您可以使用问题中提到的 std::forward 的好处。但对我来说,它还不够优雅。 - ony

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接