【问题标题】:How to make my vector class support direct initialization/assignment如何让我的向量类支持直接初始化/赋值
【发布时间】:2016-10-14 02:20:03
【问题描述】:

我的问题肯定很愚蠢,但是我对 C++11 和模板编程的理解有很多漏洞,以至于我不知道如何解决这个问题。

我正在推出我自己的非常简单的线性代数库:

typedef short index_t;

template<int M, int N, typename T = double>
class mat {
    // may want some specialized constructors for mat<1,N,T> and mat<M,1,T>
public:
    T& operator()(index_t i, index_t j) {
        return buf[i + j*M];
    }
    T& operator[](index_t k) { // useful for special cases where matrix is vector
        return buf[k];
    }

    // etc...

private:
    std::array<T, M*N> buf;
}

typedef mat<2, 1, double> col2d;
typedef mat<3, 1, double> col3d;
typedef mat<1, 2, double> row2d;
typedef mat<1, 3, double> row3d;
typedef mat<2, 2, double> mat2d;
typedef mat<3, 3, double> mat3d;

我只是希望它支持一种直接分配(或至少初始化)向量(即具有单维数的矩阵)的方法。例如,我希望能够做到v = col2d(v1,v2) 或至少col2d v = {v1,v2}。我的印象是公开buf 可能会允许col2d v = {{v1, v2}},但我不喜欢公开buf 的想法。我并不热衷于为每个 (1,N) 和每个 (M,1) 编写一个专门的构造函数。我试图使库尽可能简单易读。

有什么建议吗?

【问题讨论】:

  • mat2d x = {{ 1,2,3,4 }}; 怎么样?我发现这种初始化在实践中很有用。 col2drow2d 是否应该以相同的方式初始化? `mat2d x = { as_flat, {1,2,3,4} }; 怎么样?
  • mat3d x = {diagonal, 1,2,3};?它是否必须来自一个数组或单独的{},或者采用N*M 值来转换听起来不错?在 1 到 10 的范围内,你有多疯狂地喜欢元编程?运行时边界/大小检查是否足够好?确定不想暴露buf? C++17 可以吗?
  • 初始化矩阵很好,但我宁愿做M(i,j)=mij 而不是学习元编程。当然,行和列可以以相同的方式初始化,除了在使用构造函数时的构造函数名称。采用N*M 值听起来不错。 3/10。公开buf 并非不可能——只是不情愿。没有 C++17。

标签: c++ c++11 templates template-meta-programming aggregate-initialization


【解决方案1】:
mat(std::array<T, M*N>&& buffin):buf(std::move(buffin)){}
mat(std::array<T, M*N> const& buffin):buf(buffin){}

这不会暴露buf,而是为您提供col3d x = {{{ 1,2,3 }}}; 语法。

它还允许使用平面缓冲区初始化 mat 3d。我自己觉得这很有用。

我可能会想添加

struct flat_t{constexpr flat_t(){};};
constexpr flat_t flat{};

然后在 mat 前面加上 flat_t。这可以防止意外的隐式转换,同时仍然允许return {flat, {{1,2,3}}}; 样式返回。

另一种选择是

template<class...Ts,
  std::enavle_if_t< (sizeof...(Ts)==N*M), int> =0
>
mat(flat_t, Ts&&...ts):
  buf{{std::forward<Ts>(ts)....}}
{}

这给了你

col2d{flat, 1,2};

作为有效的col2d

添加所有Ts 可以转换为T 的sfinae 测试是此路径的下一步。

【讨论】:

  • 谢谢。你能告诉我你对我刚刚发布的答案的看法吗?我对您想到的任何批评都感兴趣。
【解决方案2】:

这是我最终做的:

template<int M, int N, typename T = double>
class mat {
public:
    // constructor
    template <typename... Args>
    mat(Args... args) : buf({ args... }) {
        static_assert(sizeof...(Args) == M*N || sizeof...(Args)==0, "Wrong number of arguments in constructor.");
    }
    // etc...
}

// the following aliases replace all those typedefs in the question
template<index_t N, typename T=double>
using row = mat<1, N, T>;
template<index_t M, typename T=double>
using col = mat<M, 1, T>;

template <typename T=double, typename... Args>
col<sizeof...(Args), T> c(Args... args) {
    return col<sizeof...(Args), T>({ args... });
}
template <typename T=double, typename... Args>
row<sizeof...(Args), T> r(Args... args) {
    return row<sizeof...(Args), T>({ args... });
}

允许类似的事情

c(1.0, 2.0, 3.0); // returns a col<3>, which is a mat<3,1>
r(1.0, 2.0);      // returns a row<2>, which is a mat<1,2>
r<float>(1.0f, 2.0f); // returns a row<2, float>

如果上面最后一行中的模板函数有一种简单的方法可以从函数的参数类型推断类型参数,我很想知道。


编辑:(包含@Yakk 建议的新版本。)

typedef short index_t;

template<int M, int N, typename T = double>
class mat {
public:
    mat() {
    }
    template <typename... Args, std::enable_if_t<(sizeof...(Args)==M*N), int> = 0> // static assertion doesn't work for external detection of validity, which is needed I think to avoid signature conflict with default, copy, move constructors etc.
    mat(Args&&... args) : buf({ std::forward<Args>(args)... }) {
        //static_assert(sizeof...(Args) == M*N || sizeof...(Args) == 0, "Wrong number of arguments in constructor.");
    }
public:
    T& operator()(index_t i, index_t j) {
        return buf[i + j*M];
    }
    T& operator[](index_t k) {
        return buf[k];
    }

    // etc... various matrix operations

private:
    std::array<T, M*N> buf;
};

// etc... various matrix operators

// aliases for row and col
template<index_t N, typename T=double>
using row = mat<1, N, T>;
template<index_t M, typename T=double>
using col = mat<M, 1, T>;

// short-hand "literals"
template <typename... Args>
col<sizeof...(Args), typename std::common_type<Args...>::type> c(Args&&... args) {
    return col<sizeof...(Args), typename std::common_type<Args...>::type>({ std::forward<Args>(args)... });
}
template <typename... Args>
row<sizeof...(Args), typename std::common_type<Args...>::type> r(Args&&... args) {
    return row<sizeof...(Args), typename std::common_type<Args...>::type>({ std::forward<Args>(args)... });
}

【讨论】:

  • 您应该在Args... 上使用转发引用和标准转发。与 SFINAE 测试相比,静态断言会生成更好的错误消息,但会阻止外部检测构造函数的有效性。您可以修改cr 以采用(T arg0, Args... args),如果未显式传递,则从第一个参数推导出T。您还可以使用std::common_typeArgs... 推导出T 类型,添加一个typename Explicit=void,如果作为第一个模板参数传递给cr,则会覆盖该决定。
  • @Yakk 感谢您提供非常有用的建议!当谈到一些 c++11 魔法时,我了解得很少,但我确实了解它所完成的工作的要点,并且以某种方式设法采纳了您的建议。不过,我不知道如何采纳您的最后一个建议,并且愿意。我知道这不是代码审查论坛,但您介意查看我的新代码(附加到此答案)并告诉我您想到的任何简单易用的改进吗?
  • template&lt;class T0, class T1, class...Args&gt; struct get_type{using type=T0;}; template&lt;class T1, class...Args&gt; struct get_type&lt;void, T1, Args...&gt;{using type=T1;}; template&lt;class T0, class T1, class...Ts&gt;using get_type_t=typename get_type&lt;T0,T1,Ts...&gt;::type; 将得到T0,除非它是无效的,在这种情况下它得到T1。将其扩展为获取 std::common_type&lt;T1, Ts...&gt;::type 留作练习。
  • @Yakk 这对我来说太深入了 Turing tarpit。我真的很高兴你已经把我带到了哪里。再次感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多