Trade-Ideas Philip
Trade-Ideas Philip

Reputation: 1247

What's the right way to call static_assert(false)?

I’m trying to use static_assert to force something to fail. If you try to instantiate a specific templated function in a specific way I want to generate a complier error. I could make it work, but it was really ugly. Is there an easier way to do this?

This was my first attempt. This did not work at all. It always generates an error, even if no one tries to use this function.

template< class T >
void marshal(std::string name, T  *value)
{
  static_assert(false, "You cannot marshal a pointer.");
}

Here’s my second attempt. It actually works. If you don’t call this, you get no error. If you do call this, you get a very readable error message that points to this line and points to the code that tried to instantiate it.

template< class T >
void marshal(std::string name, T  *value)
{
  static_assert(std::is_pod<T>::value && !std::is_pod<T>::value, "You cannot marshal a pointer.");
}

The problem is that this code is ugly at best. It looks like a hack. I’m afraid the next time I change the optimization level, upgrade my compiler, sneeze, etc, the compiler will realize that this second case is the same as the first, and they will both stop working.

Is there a better way to do what I’m trying to do?

Here’s some context. I want to have several different versions of marshal() which work for different input types. I want one version that uses a template as the default case. I want another one that specifically disallows any pointers except char *.

void marshal(std::string name, std::string)
{ 
  std::cout<<name<<" is a std::string type."<<std::endl;
}

void marshal(std::string name, char *string)
{
  marshal(name, std::string(string));
}

void marshal(std::string name, char const *string)
{
  marshal(name, std::string(string));
}

template< class T >
void marshal(std::string name, T value)
{
  typedef typename std::enable_if<std::is_pod<T>::value>::type OnlyAllowPOD;
  std::cout<<name<<" is a POD type."<<std::endl;
}

template< class T >
void marshal(std::string name, T  *value)
{
  static_assert(false, "You cannot marshal a pointer.");
}

int main (int argc, char **argv)
{
  marshal("should be pod", argc);
  marshal("should fail to compile", argv);
  marshal("should fail to compile", &argc);
  marshal("should be std::string", argv[0]);
}

Upvotes: 11

Views: 6865

Answers (5)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275966

Prior to a DR against , there was no way to do this. You might be able to make it work on your compiler, but the resulting program is ill formed no diagnostic required.

Use =delete.

template< class T >
void marshal(std::string name, T  *value) = delete;

After 's DR 2518 was resolved, the wording was tweaked:

In a static_assert-declaration, the constant-expression is contextually converted to bool and the converted expression shall be a constant expression (7.7 [expr.const]). If the value of the expression when so converted is true or the expression is evaluated in the context of a template definition, the declaration has no effect. Otherwise, the static_assert-declaration fails, the program is ill-formed, and the resulting diagnostic message (4.1 [intro.compliance]) should include the text of the string-literal, if one is supplied.

the bolded text means that a static_assert(false, "whatever") in a template definition but not instantiation doesn't cause your program to be in violation of the C++ standard. In addition, clauses that require all templates to have a valid instantiation where relaxed to exclude failures caused by static_asserts.

The result is this example from the amended standard:

template <class T>
void f(T t) {
  if constexpr (sizeof(T) == sizeof(int)) {
    use(t);
  } else {
    static_assert(false, "must be int-sized");
  }
}

being perfectly a-ok, with the failure occurring if you call f with a non-int sized T.

Defect reports are considered retroactive changes - so a conforming compiler must implement the defect report, and can do so without having to say they are using a 'newer' version of the standard. Not all compilers will do so, both because most C++ compilers tend to be incomplete implementations of a standard, and because they may have been written before was redefined.

Upvotes: 17

C-Zihao
C-Zihao

Reputation: 1

In fact, I meet the same problem as you.My static_assert(false) perform well in gcc-9. But when I use gcc-7 as my compiler, it will happen. So the best way may be upgrade your gcc or compiler version.

Upvotes: 0

cpplearner
cpplearner

Reputation: 15938

I don't understand why you have template< class T > void marshal(std::string name, T *value) in the first place. This should just be a static_assert in the primary template.

That is, you should change the definition of your primary template to

template< class T >
void marshal(std::string name, T value)
{
  static_assert(std::is_pod<T>::value);
  static_assert(!std::is_pointer<T>::value);
  std::cout<<name<<" is a POD type."<<std::endl;
}

Upvotes: 2

Quentin
Quentin

Reputation: 63154

Relying on a contradiction is not the best indeed, but there's a simpler way:

template <class...>
struct False : std::bool_constant<false> { };

template <class T>
void bang() {
    static_assert(False<T>{}, "bang!");
}

Why does this not fall under the "no valid specialization" case?
Well, because you can actually make a valid specialization, with that second half of the code:

template <>
struct False<int> : std::bool_constant<true> { };

int main() {
    bang<int>(); // No "bang"!
}

Of course, no one is actually going to specialize False to break your assertions in real code, but it is possible :)

Upvotes: 6

W.F.
W.F.

Reputation: 13988

What you are trying to do is doomed to be ill-formed (even your workaround can fail) according to [temp.res]/8 (emphasis mine):

Knowing which names are type names allows the syntax of every template to be checked. The program is ill-formed, no diagnostic required, if:
- no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, or (...)

Upvotes: 3

Related Questions