views:

524

answers:

2

I implemented a "discriminated union" capable of holding C++ types, even if they have destructors etc. I implemented this as a Russian doll; i.e. Union<T1, T2, T3> derives from unionNode<T1, <UnionNode<T2, UnionNode<T3, void> > > and UnionNode<T, Tail> derives from Tail. The specialization UnionNode<T, void> holds a void* which contains the actual data, and an index to store the current type.

For quite a few functions, I'd like a meta-function to determine if a type U is one of the types T1..Tn. For instance, I've got a member template<typename U> union_cast<U> that should only be instantiated for types that could actually be in the union. Trying to cast a Union to std::string is a compile-time error.

The simple solution was to add a template <typename U> struct inUnion :

template <typename T, typename Tail> // Tail will be a UnionNode too.
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        typedef Tail::inUnion<U> dummy; // <<< Q: where to add typename/template here?
    };
    template< > struct inUnion<T> {
    };
};
template <typename T> // For the last node Tn.
struct UnionNode<T, void> {
    // ...
    template<typename U> struct inUnion {
        char fail[ -sizeof(U) ]; // Cannot be instantiated for any U
    };
    template< > struct inUnion<T> {
    };
};

The idea is simple, I think: Each UnionNode<T, Tail> defines a template specialization inUnion<T>, and forwards the generic case inUnion<U>. If in the last node, no specialization of inUnion<U> has been found, then U is not one of the types T1...Tn, and I try to typedef an array with negative size. The problem I have is in the typedef Tail::inUnion<U> dummy line. I'm fairly certain that inUnion is a dependent name, and VC++ is quite right in choking on it. I also know that I should be able to add "template" somewhere to tell the compiler that inUnion is a template-id. But where exactly? And should it then assume that inUnion is a class template, i.e. inUnion<U> names a type and not a function?

+2  A: 
typedef typename Tail::inUnion<U> dummy;

However, I'm not sure you're implementation of inUnion is correct. If I understand correctly, this class is not supposed to be instantiated, therefore the "fail" tab will never avtually fails. Maybe it would be better to indicates whether the type is in the union or not with a simple boolean value.

template <typename T, typename TypeList> struct Contains;

template <typename T, typename Head, typename Tail>
struct Contains<T, UnionNode<Head, Tail> >
{
    enum { result = Contains<T, Tail>::result };
};

template <typename T, typename Tail>
struct Contains<T, UnionNode<T, Tail> >
{
    enum { result = true };
};

template <typename T>
struct Contains<T, void>
{
    enum { result = false };
};

PS: Have a look at Boost::Variant

PS2: Have a look at typelists, notably in Andrei Alexandrescu's book: Modern C++ Design

Luc Touraille
inUnion<U> would be instantiated, if you for instance tried to call Union<float,bool>::operator=(U) with U==int. It calls a private set(U, inUnion<U>* = 0).
MSalters
And the work with result=true/false is that I'd need boost::enable_if< >, which is incompatible with our current OSX toolchain. The separate template is still a good idea, though.
MSalters
Luc means the typedef Tail::inUnion<U> dummy; line. that will instantiate Tail. but not inUnion<U>. it gets instantiated when it needs the full definition of it. that happens for example if you take the sizeof, or access a member (using ::foo). @MSalters anyway, you've got another problem:
Johannes Schaub - litb
-sizeof(U) is never negative :) because size_t is an unsigned integer type. you will get some very high number. you probably want to do sizeof(U) >= 1 ? -1 : 1 or similar :)
Johannes Schaub - litb
i would just leave it undefined and only declare it: template<typename U> struct inUnion; so it certainly can't be instantiated. i think having it with the sizeof, the compiler is allowed also to give you an error even if you *not* instantiate it, because if knows sizeof(U) is always >=1 and ...
Johannes Schaub - litb
... then char f[sizeof(U) >= 1 ? -1 : 1] or -sizeof(U) is never valid. i read it long time ago but today morning i found the paragraph again: 14.6/7 . it's not required to refuse it but it may do that. however if you just put only the declaration of the template, it's all fine.
Johannes Schaub - litb
+4  A: 

This is easy: Use any of the two if the typename or template is qualified with a dependent type or expression. In your case, it is by Tail which is actually the simplest case of a dependent type - template parameter itself. It could be this (in a member function) or some more complicated type.

If you qualify a template, you put the template keyword directly before it. And if it is a type - which a template-id for a class template names - you put typename before the whole qualified name. In your case it is

typedef typename Tail::template inUnion<U> dummy;

If inUnion would be a function template, the typename would be dropped because you would reference a function template specialization instead.

By the way, i think you will get into trouble with the way that you do it. You cannot place specializations of types into a non-namespace scope (like you do, putting isUnion<T> into a class-scope). You also cannot specialize your template at namespace scope, because you would get this pattern:

template<typename T, typename U>
template<> struct UnionNode<T, U>::inUnion<T> { ... };

That's syntactically illegal: If you declare an explicit specialization, template<> may not be prefixed by a template<...> that contains formal parameters. Speaking english: You must also explicitly, fully, specialize all outer templates too.

As for a concrete possible implementation, why not just add two typedefs for T and Tail that can be looked up later and compared to a type recursively? Normally you want to add typedefs anyway. Another option is to partially specialize a contains class template, matching on the template parameters of an UnionNode<T, U> (like the other answer showed).

Johannes Schaub - litb