【问题标题】:Can this be accomplished with templates in C++?这可以用 C++ 中的模板来完成吗?
【发布时间】:2013-04-27 07:38:06
【问题描述】:

这是一个非常简单的类,代表三行双精度数,每行附加一些字符串信息:

struct ThreeRows {
  std::vector<double> row1;
  std::vector<double> row2;
  std::vector<double> row3;

  std::string row1info;
  std::string row2info;
  std::string row3info;
};

我想做的是通过以下方式概括这种类型:

  1. 行数不应再固定为三,但应支持任意数量的行作为类型的一部分。

  2. 我应该能够指定每一行应该是什么类型。也许我想要第一行的double 和第二行的int。 (使这个例子成为一个两行类。)

  3. 最后,我应该能够将除strings 之外的其他信息附加到行。例如(继续第 2 点中的示例),我可能想将 string 附加到第一行,但将自定义 rss_feed 附加到第二行。

如果模板允许(他们不允许),我想输入这样的内容来获取我的类型:

Rows<{double,int}, {string,rss_feed}>

从中确定的行数,或者如果我真的必须这样做:

Rows<{double,int}, {string,rss_feed}, 2>

但这是不可能的。

可以做些什么,如果可以,我将如何使用这个类模板?我将如何实际获取我的向量和信息对象并使用它们?

【问题讨论】:

  • 如果您可以使用 c++11 和 c++03 的一些预处理器乐趣,使用可变参数模板似乎是可行的。 struct rows&lt; std::pair&lt;double, int&gt;, std::pair&lt;std::string, rss_feed&gt;, std::pair&lt;double, std::string&gt;, ... &gt; 本质上是std::tuple 的一种。
  • 一切都可以用 C++ 中的模板完成...一切。
  • 看看Boost.Fusion

标签: c++


【解决方案1】:

使用一些模板魔法,您的问题可以通过可变参数模板和模板递归来解决。

template< size_t N, typename T, typename U, typename... Ts >
struct Rows : public Rows<N-1, Ts...> {
  vector<T> row;
  U rowinfo;
};

template<typename T, typename U>
struct Rows<1, T, U> {
  vector<T> row;
  U rowinfo;
};

Rows< 3, int, string, float, string, double, rss_feed > rows; 

=>  struct Rows {
       vector<int> v1;
       string s1;
       vector<foat> v2;
       string s2;
       vector<double> v3;
       rss_feed feed;
    }

要访问这些字段的值,您可以实现一个模板化的get() 成员函数

template< size_t N, typename T, typename U, typename... Ts >
struct Rows : public Rows<N-1, Ts...> {
  vector<T> row;
  U rowinfo;

  template< size_t M >
  typename enable_if< M == N, tuple<vector<T>&, U&> >::type
  get() {
    return make_tuple( ref(row), ref(rowinfo) );
  }   
};

template<typename T, typename U>
struct Rows<1, T, U> {
    vector<T> row;
    U rowinfo;

    template< size_t M >
    typename enable_if< M == 1, tuple<vector<T>&, U&> >::type
    get() {
        return make_tuple( ref(row), ref(rowinfo) );
    }

};

花点时间消化一下。 get() 方法返回一个包含对请求字段的引用的元组,并且仅当传入的数字与类的模板匹配时才启用它们,那么其他数字呢?好吧,我们可以像这样启用它们

template< size_t M >
typename enable_if< M != N, tuple<vector<T>&, U&> >::type
get() {
    return Rows<N-1, Ts...>::template get<M>();  // call parent's get,  
                                                 // ::template is required to avoid ambiguity

} 

但这是错误的!!!返回类型不是tuple&lt;vector&lt;T&gt;&amp;, U&amp;&gt;,应该是tuple&lt;vector&lt;A&gt;&amp;, B&amp;&gt; 其中AB是可变参数模板Ts...中对应的类型@

但是我们到底是如何从Ts... 获得我们想要的类型呢,我们可以通过以下技术找出可变参数模板的类型。

template< size_t N, typename... Ts >
struct variadic_type;

template< typename T, typename U, typename... Ts >
struct variadic_type< 0, T, U, Ts... > {
    typedef T T_type;
    typedef U U_type;
};

template< size_t k, typename T, typename U, typename... Ts >
struct variadic_type< k, T, U, Ts... > {
    typedef typename variadic_type< k-1, Ts... >::T_type T_type;
    typedef typename variadic_type< k-1, Ts... >::U_type U_type;
};

所以variadic_type&lt; 1, int, int, short, short, float, float &gt;::T_typeU_type 将是shortshort

现在我们有一种方法可以找出可变参数模板上特定位置的类型,我们可以将其应用于我们错误的get() 方法以获得正确的返回类型...

template< size_t M >
typename
    enable_if< M != N,
                tuple<
                    vector<typename variadic_type< N-M, T, U, Ts...>::T_type>&,
                    typename variadic_type< N-M, T, U, Ts...>::U_type&
                > >::type
get() {
    return Rows<N-1, Ts...>::template get<M>();
}

我们已经完成了,我们可以拨打get()like

Rows< 3, int, string, double, string, float, string > rows;

auto r3 = rows.get<3>();  //  { vector<int>& v, string& s };
auto r2 = rows.get<2>();  //  { vector<double>& v, string& s };
auto r1 = rows.get<1>();  //  { vector<float>& v, string& s };

auto r4 = rows.get<4>();  //  error:: other numbers will not compile!!

现在索引规则不太好处理,但我认为这可以理解。

【讨论】:

  • 或者,如果您将 2 个相关字段包装到一个类中,您可以使用原始实现来完成。
  • 这是一个有趣的答案。我试图编译您的实现,但我得到以下编译时错误 =“模板参数的数量错误(2,应该是 3 或更多)”与行 struct Rows : public Rows&lt;N-1, Ts...&gt; { 相关联。
  • @deepak 对我来说编译得很好,对于上面的例子,只有 3 个可以工作。
  • 另一天,StackOverfow 上的另一个 awesome 答案。
  • @7cows 我在 clang 上进行了测试,它成功了。 this thread 如果你有兴趣,可以谈谈学习 C++ 元编程的书籍。
【解决方案2】:

这可以很容易地使用std::tuple 来指定您的类型列表。我们需要做的就是声明主模板接受两个参数,然后创建一个部分特化,其中这些类型参数是元组。在偏特化中,我们可以使用参数推导来捕获元组的模板参数,并将它们重用于我们的目的。我们可以创建一个新模板来指定类型列表(即Types&lt;int,double&gt;),但是在这种情况下元组特别好,因为无论如何您都需要有一种访问各个行的方法,以及@987654323 @ 通过std::get&lt;i&gt; 提供了一种内置方式来执行此操作。将元组用于模板参数可能会使您使用std::get 访问行更加明显。

这是一个完整的例子:

#include <string>
#include <tuple>
#include <vector>

// primary template
template <typename RowTuple,typename RowInfoTuple> struct Rows;

// variadic partial specialization 
template <typename... RowTypes,typename... RowInfoTypes>
struct Rows<std::tuple<RowTypes...>,std::tuple<RowInfoTypes...>>
{
  // use variadic expansion to make a tuple of vectors
  std::tuple<std::vector<RowTypes>...> rows;
  std::tuple<RowInfoTypes...> rowinfos;
};

struct rss_feed { };

int main(int,char**)
{
  Rows<
    std::tuple<double,int>,
    std::tuple<std::string,rss_feed>
  > data;

  std::get<0>(data.rows).push_back(1.5);
  std::get<1>(data.rows).push_back(2);
  std::get<0>(data.rowinfos) = "info";
  std::get<1>(data.rowinfos) = rss_feed();
  return 0;
}

【讨论】:

  • 这比 yngum 的解决方案更好(也更简单)!他的解决方案是在重新发明元组轮吗?另一件事:就像 yngum 一样(出于不同的目的),您首先声明一个类 Rows,然后在下面定义它。这是使用模板时常用的技巧吗?这通常在什么时候使用?你能详细说明一下吗?
  • @7cows:我已经稍微扩展了我的答案来谈论部分专业化。我会说部分专业化相当普遍。它非常强大。它是所有 std 类背后的机制,为您提供有关 标头中定义的类型的信息。主模板只有一个特化可能不太常见。
  • @7cows:在这种情况下,我们使用它来解决您无法在主模板中进行更复杂的参数规范的事实,例如template &lt;class std::tuple&lt;typename... RowTypes&gt;,class std::tuple&lt;typename... RowInfoTypes&gt; &gt;
【解决方案3】:

如果您在编译时知道(有限的)类型,我会使用 boost::variant 来执行此操作:

typedef boost::variant<double, int> DataType;
typedef boost::variant<string, rss_feed> MetaDataType;

struct Row
{
    DataType data_;
    MetaDataType meta_data_;
};

template <int NUM_ROWS>
struct Rows
{
    Row row_data_[NUM_ROWS];
};

这并没有为您提供相当您似乎暗示的每行的固定类型,但它应该可以解决您的一般问题。

【讨论】:

  • 这比它可能的类型安全性更低。我们知道两行中的一个应该保存双精度,而另一行应该保存整数,我们可以使用类型系统强制执行此操作,但您的示例将允许两行存储双精度数或两行存储整数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多