Boris
Boris

Reputation: 1437

Partial template specification of template member function to disable instantiation of class with template parameter

The idea behind the following code is to enable the class only for types with certain sizes, like 1 in the following example:

#include <assert.h>

template<typename T>
class X
{
  private:

  template<int S=sizeof(T)>
  inline void foo(void) {static_assert(false,"illegal type");}

};

template<typename T> template<int S>
inline void X<T>::foo<1>(void){};

int main()
{

  X<char> x;
  return 0;
}

However this doesn't compile in gcc, which fails with the following error message:

q.cpp: In member function ‘void X<T>::foo()’:
q.cpp:9:26: error: static assertion failed: illegal type
   inline void foo(void) {static_assert(false,"illegal type");}
                          ^~~~~~~~~~~~~
q.cpp: At global scope:
q.cpp:14:30: error: non-class, non-variable partial specialization ‘foo<1>’ is not allowed
 inline void X<T>::foo<1>(void){};
                              ^
q.cpp:14:13: error: redefinition of ‘void X<T>::foo()’
 inline void X<T>::foo<1>(void){};
             ^~~~
q.cpp:9:15: note: ‘void X<T>::foo()’ previously declared here
   inline void foo(void) {static_assert(false,"illegal type");}

What would be the right syntax to define foo to achieve the desired results?

Upvotes: 0

Views: 44

Answers (2)

Brian Bi
Brian Bi

Reputation: 119099

Your code has a few issues.

  1. A template for which no valid specialization can be generated makes the program ill-formed, even if the template is never instantiated ([temp.res]/8.1). This implies that static_assert(false, "illegal type") can never be part of a well-formed program; you must make the condition dependent on a template parameter.

  2. It is not possible to declare an explicit (full) specialization for a member of an unspecialized class template ([temp.expl.spec]/16), which is what you're trying to do with X<T>::foo.

  3. Even if not for the above, if you changed the static_assert condition to S == 1, it couldn't prevent the enclosing class template from being instantiated because (a) instantiating a class template doesn't automatically instantiate the definitions of its member functions, and (b) providing default arguments for all template parameters doesn't automatically cause the template to be instantiated with those default arguments. The instantiation would only occur if you called x.foo() or did something else requiring the definition to exist.

Instead of this song and dance, it's better to put the static_assert directly in the class:

template<typename T>
class X {
    static_assert(sizeof(T) == 1, "illegal type");
    // ...
};

It's legal to put static_assert there because static_assert is a declaration, not an expression.

Upvotes: 1

Rakete1111
Rakete1111

Reputation: 48938

Your version fails because static_assert(false) is always false and the compiler can diagnose it early on. The common trick here is to make the condition depend on a template parameter and to trick the compiler into thinking it can't evaluate the condition, but in your case, you can do this easier:

template<typename T>
class X
{
    static_assert(sizeof(T) == 1, "illegal type");
};

Now as you can see, the condition clearly depends on T and so the compiler will only diagnose the static_assert when instantiating X with a specific T, which is exactly what you want.

Upvotes: 3

Related Questions