1

我正在尝试创建一个模板“AutoClass”,该模板创建一个具有任意成员集的任意类,例如:

AutoClass<int,int,double,double> a;
a.set(1,1);
a.set(0,2);
a.set(3,99.7);
std::cout << "Hello world! " << a.get(0) << " " << a.get(1) << " " << a.get(3) << std::endl;

到目前为止,我有一个带有工作“集合”成员的 AutoClass:

class nothing {};

template <  typename T1 = nothing, typename T2 = nothing, typename T3 = nothing,
            typename T4 = nothing, typename T5 = nothing, typename T6 = nothing>
class AutoClass;

template <>
class AutoClass<nothing, nothing, nothing,
                nothing, nothing, nothing>
{
    public:
    template <typename U> void set(int n,U v){}
};

template <  typename T1, typename T2, typename T3,
            typename T4, typename T5, typename T6>
class AutoClass: AutoClass<T2,T3,T4,T5,T6>
{
    public:
    T1 V;
    template <typename U> void set(int n,U v)
    {
        if (n <= 0)
            V = v;
        else
            AutoClass<T2,T3,T4,T5,T6>::set(n-1,v);
    }
};

我开始在实施相应的“get”时遇到问题。这种方法不能编译:

template <  typename T1, typename T2, typename T3,
            typename T4, typename T5, typename T6>
class AutoClass: AutoClass<T2,T3,T4,T5,T6>
{
    public:
    T1 V;
    template <typename U> void set(int n,U v)
    {
        if (n <= 0)
            V = v;
        else
            AutoClass<T2,T3,T4,T5,T6>::set(n-1,v);
    }
    template <typename W> W get(int n)
    {
        if (n <= 0)
            return V;
        else
            return AutoClass<T2,T3,T4,T5,T6>::get(n-1);
    }
    template <> T1 get(int n)
    {
        if (n <= 0)
            return V;
        else
            return AutoClass<T2,T3,T4,T5,T6>::get(n-1);
    }
};

此外,似乎我需要为<nothing, nothing, nothing, nothing, nothing, nothing>专业化实施 get 。关于如何解决这个问题的任何想法?

4

4 回答 4

3

首先,我更喜欢Boost.FusionBoost.Tuple因为我认为它支持模板元编程和运行时算法的更好混合。

例如,我想向您展示一个小奇迹:

struct Name {}; extern const Name name;
struct GivenName {}; extern const GivenName givenName;
struct Age {}; extern const Age age;

class Person
{
public:
  template <class T>
  struct value
  {
    typedef typename boost::fusion::result_of::at_key<data_type const,T>::type type;
  };

  template <class T>
  struct has
  {
    typedef typename boost::fusion::result_of::has_key<data_type,T>::type type;
  };

  template <class T>
  typename value<T>::type
  get(T) { return boost::fusion::at_key<T>(mData); }

  template <class T>
  Person& set(T, typename value<T>::type v)
  {
    boost::fusion::at_key<T>(mData) = v; return *this;
  };

private:
  typedef boost::fusion::map <
    std::pair<Name, std::string>,
    std::pair<GivenName, std::string>,
    std::pair<Age, unsigned short>
  > data_type;
  data_type mData;
};

使用起来真的很有趣:

Person p;
p.set(name, "Rabbit").set(givenName, "Roger").set(age, 22);

好吧,我自己更喜欢按类索引而不是按索引,因为我可以传达含义以及添加类型检查;)

于 2010-03-26T18:43:24.337 回答
3

我是否可以推荐使用 Boost 库的大量(并且经过良好测试和跨平台的)模板魔术类集?听起来您正在寻找的是boost::tuple。任何时候你可以不编写自己的代码而侥幸——尤其是在使用模板的复杂情况下——你应该使用别人的。

于 2010-03-26T17:26:57.653 回答
1

正如其他人所提到的,您可能应该能够通过重用 Boost 或其他地方的现有实现来达到您想要的位置。

如果您将做一些使用这些无法完成的事情,或者您很好奇:

  • 尝试将伪变量模板排除在实现之外
  • 使用类型列表来允许递归元函数等。
  • 如果需要,使用伪可变参数模板作为接口转发给实现
  • 在编译时尽可能多地做,尤其是检查索引等。

为了方便起见,使用 MPL 的一种简单方法可能如下所示:

template<class Types, size_t N> struct holder 
  // recursively derive from holder types:
  : holder<Types, N-1> 
{
    typename boost::mpl::at_c<Types,N>::type value;
};

// specialization that terminates the recursive derivation:
template<class Types> struct holder<Types,0> {
    typename boost::mpl::at_c<Types,0>::type value;
};

template<class Types>
class AutoClass 
  // recursively derive from holder types:
  : holder<Types, boost::mpl::size<Types>::value-1>    
{
    enum { n = boost::mpl::size<Types>::value };
public:
    template<size_t N, class U> void set(const U& u) {
        // index check at compile time:
        BOOST_STATIC_ASSERT((N < n));
        // cast to responsible holder base:
        static_cast<holder<Types,N>*>(this)->value = u;
    }
    template<size_t N> typename boost::mpl::at_c<Types,N>::type get() const { 
        // index check at compile time:
        BOOST_STATIC_ASSERT((N < n));
        // cast to responsible holder base:
        return static_cast<const holder<Types,N>*>(this)->value;
    } 
};

用法:

typedef boost::mpl::vector<int,std::string> Types;
AutoClass<Types> a;

a.set<0>(42);
assert(a.get<0>() == 42);
a.set<1>("abcde");
assert(a.get<1>() == "abcde");

请记住,这仍然可以使用伪可变参数模板进行包装,以方便最终用户。

于 2010-03-26T18:28:57.923 回答
0

由于您的基本情况,您需要实现 <nothing, nothing...> 。考虑:

template <typename W> W get(int n)
{
    if (n <= 0)
        return V;
    else
        return AutoClass<T2,T3,T4,T5,T6>::get(n-1);
}

考虑一下当您在 n 为 5 的完整 AutoClass 上调用此函数时会发生什么。它会创建一个具有 5 个成员的 autoclass 并使用 n = 4 进行调用......直到它再次到达这一点:

template <typename W> W get(int n) // current autoclass is <T6,nothing,nothing...>
{
    if (n <= 0)
        return V;
    else
        return AutoClass<T2,T3,T4,T5,T6>::get(n-1); // this is <nothing, nothing...>
}

当然,对这个自动类的调用不会发生,但编译器无论如何都必须编译该代码,因为你已经告诉它了。

您还需要创建一个 AutoClass<nothing,...>::get 因为 n 可能是 1093。

您当前的界面无法解决此问题。如果您将 n 放在模板参数中,您可以制作一个不会这样做的特殊情况。在这种情况下,您不能。我认为你会遇到很多问题,因为你选择了这个很难解决的界面。例如,当 W 为 'int' 但 AutoClass::get(n-1) 返回一个 double 或更糟,完全不兼容的东西时会发生什么?

于 2010-03-26T17:37:26.857 回答