문제

I am attempting to create a template "AutoClass" that create an arbitrary class with an arbitrary set of members, such as:

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;

By now I have an AutoClass with a working "set" member:

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);
    }
};

and I started to have problems implementing the corresponding "get". This approach doesn't compile:

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);
    }
};

Besides, it seems I need to implement get for the <nothing, nothing, nothing, nothing, nothing, nothing> specialization. Any Idea on how to solve this?

도움이 되었습니까?

해결책

First of all, I prefer Boost.Fusion to Boost.Tuple as it supports a better mixin of template metaprogramming and runtime algorithms I think.

For example, I'd like to present you a little marvel:

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;
};

It's really fun to use:

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

Well, I myself prefer indexing by classes than by indices, because I can convey meaning as well as adding type checking ;)

다른 팁

Might I recommend using the Boost library's extensive (and well-tested and cross-platform) set of template-magicky classes? It sounds like what you're looking for is boost::tuple. Any time you can get away with not writing your own code—especially in a complicated situation with templates—you should use someone else's.

As others mentioned, you probably should be able to get where you want by reusing existing implementations from Boost or elsewhere.

If you would be doing something that can't be done using those or if you're curious:

  • try to keep the pseudo-variadic templates out of the implementation
  • use type-lists instead to allow for recursive meta-functions etc.
  • use pseudo-variadic templates as an interface if needed that forwards to the implementation
  • do as much at compile-time as possible, especially checks for indices etc.

A simple approach, utilizing MPL for convenience could look something like this:

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;
    } 
};

Usage:

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");

Keep in mind that this can still be wrapped with pseudo-variadic templates for end-user-convenience.

You need to implement for <nothing, nothing...> because of your base case. Consider:

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

Consider what happens when you call this function on a full AutoClass with an n of 5. It creates an autoclass with 5 members and calls with n = 4....and again until it reaches this point:

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...>
}

Sure, the call to this autoclass won't happen but the compiler has to compile that code anyway because you've told it to.

You also need to make an AutoClass<nothing,...>::get because n could be 1093.

I don't see a way out of this with your current interface. If you put the n in the template argument you could make a special case that wouldn't do this. In this case you cannot. I think you're going to run into a lot of issues because you've chosen this interface that are going to be rather difficult to solve. For example, what happens when W is 'int' but AutoClass::get(n-1) returns a double or worse, something totally incompatible?

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top