22

I'm trying to understand the usefulness of static_assert, and I want to know if it can help me in enforcing a design, and if so, how.

I have a general template class that hides its own implementation inside another template class which is partially specialized based on the size of the template type. Here's a brief outline of this design:

template <class T, size_t S = sizeof(T)>
struct Helper;

template <class T>
struct Helper<T, sizeof(long)>
{
    static T bar();
};

// ... other specializations ...

template <class T>
class Foo
{
public:

    T bar()
    {
        return Helper<T>::bar();
    }
};

Foo is only supported if size of T is supported by a specialization of Helper. For example, Foo<long> and Foo<unsigned long> are both supported. However, suppose the user tries to construct a Foo<bool>. Normally, this would generate errors because the specialization of Helper for bool isn't defined, which is intended behaviour.

Is there any way to use static_assert in this design to provide more helpful errors to the user of this interface?

Additionally, I'd like to also restric the user from using a specific type, even though the size might be correct. For example, Foo<float> shouldn't be allowed. Right now, the only way I know of enforcing this is through a bold comment in the documentation. :)

Zeenobit
  • 4,954
  • 3
  • 34
  • 46
  • Think more generally, is it just integer types that are supported? No `char`, `bool`, `float`, et al? – Rapptz Jul 16 '13 at 14:29
  • Search SO for template type constraints or "concepts". A facility to do this was removed from C++11 at the last minute. There are less automatic ways to achieve similar results though. – luke Jul 16 '13 at 14:29
  • @Rapptz, `char`, `int`, and `long`, along with their `unsigned` versions should be supported. The code would be identical for `int`, `long`, and `unsigned long` if `sizeof(int) == sizeof(long) == sizeof(unsigned long)`. – Zeenobit Jul 16 '13 at 14:31

3 Answers3

30

If it can only work for a specialization of the template class, then have the default template class raise a static assert:

template <class T, size_t S = sizeof(T)>
struct Helper
{
   static_assert(sizeof(T) == -1, "You have to have a specialization for Helper!" );
}

The default template class will only be chosen if there isn't a better specialization, therefore the assert will be risen.

You can use the same technique to disallow types, but you'll need another template parameter that will be used for the static assert check.

template <class T, class G = T, size_t S = sizeof(T)>
struct Helper
{
   static_assert(sizeof(G) == -1, "You have to have a specialization for Helper!" );
}

template <class G>
struct Helper<float,G>
{
   static_assert(sizeof(G) == -1, "You can't use float !" );
}

template <>
struct Helper<int>
{
 //This is a good specialization
};

Then you can try it with these variables:

Helper<bool> a;  //"You have to have a specialization for Helper!"
Helper<float> b; //"You can't use float !"
Helper<int> c;   //compiles OK
mustafagonul
  • 1,139
  • 1
  • 15
  • 32
Yochai Timmer
  • 48,127
  • 24
  • 147
  • 185
  • 4
    I believe you want `static_assert(false,...)`, so it always trips. `static_assert` prints the error if the first argument is false. – Dave S Jul 16 '13 at 14:34
  • 16
    This won't work as is - you need to make the assertion dependent (such as `static_assert(sizeof(T) == 0, ...);`), otherwise it gets processed at point of declaration, not instantiation. – Angew is no longer proud of SO Jul 16 '13 at 14:39
  • @Angew have you checked? I'd expect it to fire at _shallow instantiation_ at first. N.m. I checked: you're right :) – sehe Jul 16 '13 at 14:44
  • I just tried this solution, and it doesn't work. It always raises an assertion whether or not the default specialization is used. Not sure if this behvaiour is dependant on the compiler. – Zeenobit Jul 16 '13 at 14:47
  • @sehe Yup, I just did. It looks like checking the sizes is the way to go. You should include it as part of the answer. :) – Zeenobit Jul 16 '13 at 14:50
  • Right, need to be instantiation dependent, otherwise the compiler will just run the assert anyway. – Yochai Timmer Jul 16 '13 at 14:58
  • @sehe You're right. So i had to open visual studio for a better solution. – Yochai Timmer Jul 16 '13 at 15:24
  • 1
    @Zeenobit I've changed the solution to work properly. Need to add another "invisible" template argument – Yochai Timmer Jul 16 '13 at 15:25
12

http://en.cppreference.com/w/cpp/header/type_traits

std::is_base_of and std::is_convertible could help with your first issue and as for the second,

static_assert(!std::is_same<float,T>(),"type can't be float");

hopefully this helps someone else who stumbles upon this question, assuming OP probably found an answer in the 4 years since it was asked :)

Carlo Wood
  • 5,648
  • 2
  • 35
  • 47
Austin_Anderson
  • 900
  • 6
  • 16
  • 1
    I had to do `std::is_same::value` in VS2010 to avoid a C2057 expected constant expression error. – PitaJ Feb 24 '21 at 22:56
1

I figured out a better solution for this problem by combining the answers and comments here.

I can define a static type checker like so:

template <class A, class B>
struct CheckTypes
{
    static const bool value = false;
};

template <class A>
struct CheckTypes<A, A>
{
    static const bool value = true;
};

Not sure if such a struct already exists in the standard library. Anyways, then in Foo, I can check for types and sizes using:

static_assert((sizeof(T) == sizeof(long) || sizeof(T) == sizeof(int)) && !CheckTypes<T, float>::value, "Error!");
gartenriese
  • 4,131
  • 6
  • 36
  • 60
Zeenobit
  • 4,954
  • 3
  • 34
  • 46