views:

145

answers:

6

Sometimes when coding with C++ templates, you want to prevent users from instantiating a specific specialization or set of specializations, because the result would be nonsensical. So you can define a (specific or partial) specialization whose definition, if instantiated, would cause a compiler error. The goal would be, if a user "misuses" the template, to cause a compiler error right next to a comment in your header file explaining what not to do, rather than leaving the compiler to come up with some confusing error message by its own devices, or maybe allowing the questionable code to compile.

Example:

template <typename T> struct MyClassTemplate {
  // ...
};

template <typename T> struct MyClassTemplate<T*> {
  // Do not use MyClassTemplate with a pointer type!
  typedef typename T::intentional_error err;
};

There are a number of ways to do this (depending on whether your specialization is a complete or partial specialization of a class or function). But the syntax used must (?) depend on a template parameter, or else the compiler will complain when it first parses the intentional-error definition. The example above has a hole in that somebody could stubbornly define an intentional_error nested type or member typedef (though I'd say they would then deserve whatever problems come up as a result). But if you use a trick too fancy, you're likely to get an indecipherable and/or misleading compiler error message, which mostly defeats the purpose.

Are there better straightforward ways to disallow template instantiations?

I'm aware that in C++0x, template Concepts and deleted function declarations will provide much better control over this sort of thing, but I'm looking for answers that are valid C++03.

+1  A: 

Concepts were removed from '0x. You can use a library, like Boost Concept Check.

Steve M
+1  A: 

boost::enable_if

Alf P. Steinbach
+9  A: 

You could just omit defining it.

template <typename T> struct MyClassTemplate<T*>;

You could also derive from a non-defined specialization

template <typename T> struct invalid;
template <typename T> struct MyClassTemplate<T*> : invalid<T> { };

Note that explicit specializations that declare classes or functions will never depend on template parameters. So, stuff like this that depend on template parameters can't work anyway. In that case, declaring a non-defined explicit specialization should be sufficient

template<> struct MyClassTemplate<int*>;
Johannes Schaub - litb
Some of these are going to give errors at link time rather than compile time, aren't they?
Zack
@Zack all will give errors at compile time.
Johannes Schaub - litb
Oh, right, because to instantiate a class template one must have a full class declaration. I was thinking of function templates.
Zack
@ Johannes: Hi, i did not get the concept of how you did this. Can you explain how this works? template <typename T> struct invalid;template <typename T> struct MyClassTemplate<T*> : invalid<T> { };
Green Code
A perverse programmer could supply definitions for your undefined templates or specializations. But of course the point is to prevent accidental instantiations, and in the end you can never perfectly protect anything from a perverse programmer.
aschepler
@aschepler: Thanks, but can you explain more about accidental instantiations? if i get this right, we want to make it impossible to replace T with int?
Green Code
@aschepler: as the saying goes, in C++ you can protect yourself from Murphy (with sometimes ugly tricks) but you cannot protect yourself from Machiavelli.
Matthieu M.
+1  A: 

If you don't want to use a library, this construct is pretty reliable (it's roughly what Boost does internally):

template <typename T>
void must_be_specialized(T const&)
{
   enum dummy { d = (sizeof(struct must_be_specialized_for_this_type)
                     == sizeof(T)) };
}

You can put something analogous in a specialization to disallow instantiation of the template with that type. I wouldn't, personally, worry about must_be_specialized_for_this_type gaining a definition from somewhere, but you could use a forward declaration to squirrel it away in a private namespace if you really wanted.

Zack
@Zack: I did not get it, if two types have the same size what will happen? Also what does the part with the enum do ?
Green Code
`struct must_be_specialized_for_this_type` is never defined anywhere, so that's what triggers the error. Using it in an expression that involves `T` is what makes the error happen at template instantiation time rather than parse time, and the `enum dummy` part is just a convenient thing to do with a constant expression so it won't get thrown away entirely at parse time.
Zack
I wasn't really aware of it either, but that struct is actually a forward declaration of a local struct of `must_be_specialized`. So it can't actually gain a definition from outside. However, note that compilers are free to error out even without instantiating (comeau actually does, and dgregor told me I should send a PR against clang so they do too, after they fixed other bugs), because there can never be a valid specialization of the template. The trick to make it valid will be to make the name dependent, like `sizeof (typename depedent<struct must_be_specialized_for_this_type>::type)`.
Johannes Schaub - litb
I could be wrong, but I don't think either of those are correct. If `struct must_be_specialized_for_this_type` had a full definition above the function template, this would compile, wouldn't it? And the presence of `sizeof(T)` makes the *entire* expression dependent, so the compiler's not allowed to issue a parse-time error.
Zack
@Zack regarding your first point: yes then it would compile - I didn't think it through. Regarding the second point: It makes the entire `x == y` expression value-dependent. This doesn't make any names in it dependent, though. Since that class name is not dependent, it has to be complete at the template definition (C++03, 14.6/79) since no valid specialization could ever be generated for it (QoI issue). My "dependent" trick above doesn't work either, because you can't pass a local type as a template type argument in C++03. I think I was too much in a hurry when I wrote that comment :)
Johannes Schaub - litb
+1  A: 

For me this sounds like a typical case for static_assert from C++0x or BOOST_STATIC_ASSERT. The static_assert functionality has the advantage that you can pass a custom error message so that the reason for the error is more clear.

Both ways are giving you the opportunity to prematurely end the compilation process under some custom defined compile time condition.

with static_assert:

template <typename T> struct MyClassTemplate<T*> {
    static_assert(always_false<T>::value, "Do not use MyClassTemplate with a pointer type!");
};

with BOOST_STATIC_ASSERT

template <typename T> struct MyClassTemplate<T*> {
    // Do not use MyClassTemplate with a pointer type!
    BOOST_STATIC_ASSERT(always_false<T>::value);
};

Always false would look something like this:

template< typename T >
struct always_false { 
    enum { value = false };  
};

HTH

Edit: Fixed the examples to make them actually work ;-) Thanks to GMan!

Vinzenz
Those will assert unconditionally, all the time. (That is, the compiler will see `false`, know this static assert fails, so trigger a diagnostic.) You need to make it dependent on a template parameter if you want it to only trigger when instantiated, like this: `static_assert(sizeof(T) == 0, "blah");`.
GMan
Dang, I totally forgot that -.-' Thanks for reminding me
Vinzenz
So I fixed the example that it actually works :-)
Vinzenz
+1  A: 

"Are there better straightforward ways to disallow template instantiations?" Nothing significantly better than what you have already identified. I am pretty sure C++ protection mechanisms are there to protect you from accident not from malice. And someone defining a specialisation or a class to break your intended use I would consider malicious. Perhaps you could hit the person in the back of the head each time they do it.

I personally prefer to put the checks into templates that exist only to describe the checks. That allows interesting combinations of inheritance and templates.

template <class T>
class not_with_pointer_t { };

template <class T>
class not_with_pointer_t<T*>;

template <class T>
class some_class_t : public not_with_pointer_t<T> { };

template <class T, template <class U> class base_t>
class another_class_t : public base_t<T> { };

typedef some_class_t<int> a_t; // ok
typedef some_class_t<void*> b_t; // error if instantiated
typedef another_class_t<void*, not_with_pointer_t> c_t; // error if instantiated

template <class T> class unrestricted_t { };
typedef another_class_t<void*, unrestricted_t> d_t; // ok
Bowie Owens