36

This issue has been discussed a few times but all the solutions I have found either didn't work or were based on boost's static assert. My problem is simple. I have a class, and I only want to allow real types (double and float). I want a compile-time error if I try to instantiate the class with a type other than float or double. I am using Visual C++ 11. Here is what I have tried:

template <typename RealType>
class A
{
  // Warning C4346
  static_assert(std::is_same<RealType, double>::value || std::is_same<RealType, float>::value);
}


template <typename RealType>
class A
{
  // Error C2062: type 'unknown' unexpected
  static_assert(decltype(RealType) == double || decltype(RealType) == float);
}

Any ideas? Thanks in advance!

jww
  • 97,681
  • 90
  • 411
  • 885
quant
  • 21,507
  • 32
  • 115
  • 211
  • 1
    The first question is, does it matter? If you instantiate the template with another type, and the type can't be treated the way the template expects to treat it, compilation will fail. And if it can, allowing *only* those two types effectively outlaws...say...a type like BigDecimal. – cHao Jun 07 '13 at 05:23
  • visual studio really issue a warning when static_assert fails, and not an error? Sounds like a bug. – BЈовић Jun 07 '13 at 06:40
  • Also see [C++ templates that accept only certain types](http://stackoverflow.com/q/874298/) and [Restrict C++ Template Parameter to Subclass](http://stackoverflow.com/q/3175219). They predate C++11, but they might make a good marker for someone else. – jww Jan 19 '16 at 21:58
  • Related, including **more very thorough C++ examples (right in the question)** of how to do exactly what you're asking in this question: [How to use static assert in C to check the types of parameters passed to a macro](https://stackoverflow.com/questions/60611626/how-to-use-static-assert-in-c-to-check-the-types-of-parameters-passed-to-a-macro) – Gabriel Staples Mar 11 '20 at 01:18

4 Answers4

41

In your first example, static_assert should take a second parameter which would be a string literal, otherwise it's deemed to fail (edit: dropping the the second parameter is legal since C++17). And this second argument cannot be defaulted.

Your second example is incorrect for several reasons:

  • decltype is meant to be used on an expression, not on a type.
  • You simply cannot compare types with ==, the correct way to do this is what you try in your first attempt with std::is_same.

So, the right way to do what you are trying to achieve is:

#include <type_traits>

template <typename RealType>
class A
{
  static_assert(std::is_same<RealType, double>::value || std::is_same<RealType, float>::value,
                "some meaningful error message");
};

Moreover, I bet you are trying to constrict your template to floating points values. In order to do this, you can use the trait std::is_floating_point:

#include <type_traits>

template <typename RealType>
class A
{
  static_assert(std::is_floating_point<RealType>::value,
                "class A can only be instantiated with floating point types");
};

And as a bonus, take this online example.

Morwenn
  • 21,684
  • 12
  • 93
  • 152
31

One solution I've seen is to use std::enable_if in a type alias. Something like:

using value_type = typename std::enable_if<
                    std::is_same<float, RealType>::value ||
                    std::is_same<double, RealType>::value,
                    RealType
                >::type;

value_type only exists if RealType is exactly float or double. Otherwise, the type is undefined and compilation fails.

I'd warn about being too strict with types, though. Templates are as powerful as they are partly because the duck typing they do means that any type that can be used the way you want to use it, will work. Disallowing types for the sake of disallowing types generally doesn't gain you much, and can make things less flexible than they could be. For example, you wouldn't be able to use a type with more precision, like a big-decimal type.

cHao
  • 84,970
  • 20
  • 145
  • 172
  • That, and if you only need two specializations, good old OO and runtime polymorphism might be a better idea (at least worth considering). – Tamás Szelei Jun 07 '13 at 07:54
  • 1
    SFINAE is really good when you want to create more specializations of the template later :) However, if zhe's sure there will never be any other specialization, `static_assert` allows to display meaningful error messages (because SFINAE ones often hurt my eyes). – Morwenn Jun 07 '13 at 08:12
6

This way it also allows specialization for various types:

template<typename T, typename Enable = void>
class A {
/// Maybe no code here or static_assert(false, "nice message");
};

/// This specialization is only enabled for double or float.
template<typename T>
class A<T, typename enable_if<is_same<T, double>::value || is_same<T, float>::value>::type> {

};
Juraj Blaho
  • 13,301
  • 7
  • 50
  • 96
  • 1
    with `static_assert(false, "nice message");` the program is ill formed, no diagnostics required. See http://stackoverflow.com/questions/30078818/static-assert-dependent-on-non-type-template-parameter-different-behavior-on-gc The issue there at first looks more complex, but the underline cause is as simple as your example. – bolov Nov 15 '15 at 12:59
2

C++20 concept usage

https://en.cppreference.com/w/cpp/language/constraints cppreference is giving as similar example for inheritance as I mentioned at: C++ templates that accept only certain types and by guessing from that I think the syntax for specific classes will be:

template <class T, class Class1, class Class2>
concept Derived = std::is_same<U, Class1>::value || std::is_same<U, Class2>::value;

template<Derived<MyClass1, MyClass2> T>
void f(T);

but I haven't been able to test this yet due to compiler support as explained in that other answer.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985