In template programming, static_assert
helps programmers to check constraint(s) on template arguments and generate human readable error messages on violation of constraint(s).
Consider this code,
template<typename T>
void f(T)
{
static_assert(T(), "first requirement failed to meet.");
static_assert(T::value, "second requirement failed to meet.");
T t = 10; //even this may generate error!
}
My thought is : if the first static_assert
fails, it means some requirement on T
doesn't meet, hence the compilation should stop, generating only the first error message — because it doesn't make much sense to continue the compilation just to generate more and more error messages, most of which often point to a single constraint violation. Hundreds of error messages, instead of just one, look very scary on the screen — I would even say, it defies the very purpose of static_assert
to some extent.
For example, if I call the above function template as:
f(std::false_type{});
GCC 4.8 generates the following:
main.cpp: In instantiation of 'void f(T) [with T = std::integral_constant<bool, false>]':
main.cpp:16:24: required from here
main.cpp:7:5: error: static assertion failed: first requirement failed to meet.
static_assert(T(), "first requirement failed to meet.");
^
main.cpp:9:5: error: static assertion failed: second requirement failed to meet.
static_assert(T::value, "second requirement failed to meet.");
^
main.cpp:11:11: error: conversion from 'int' to non-scalar type 'std::integral_constant<bool, false>' requested
T t = 10; //even this may generate error!
As you can see (online), that is too much of error. If the first static_assert
fails, it is very much likely that the rest of the code will also fail if compilation continues, then why continue compilation? In template programming, I'm sure many programmers don't want such cascading error messages!
I tried to solve this issue by splitting the function into multiple functions, in each checking only one constraint, as:
template<typename T>
void f_impl(T); //forward declaration
template<typename T>
void f(T)
{
static_assert(T(), "first requirement failed to meet.");
f_impl(T());
}
template<typename T>
void f_impl(T)
{
static_assert(T::value, "second requirement failed to meet.");
T t = 10;
}
f(std::false_type{}); //call
Now this generates this:
main.cpp: In instantiation of 'void f(T) [with T = std::integral_constant<bool, false>]':
main.cpp:24:24: required from here
main.cpp:10:5: error: static assertion failed: first requirement failed to meet.
static_assert(T(), "first requirement failed to meet.");
^
That is a lot of improvement — just one error message is a lot easier to read and understand (see online).
My question is,
static_assert
? Static assertions are a way to check if a condition is true when the code is compiled. If it isn't, the compiler is required to issue an error message and stop the compiling process. The condition that needs to be checked is a constant expression.
static_assert is meant to make compilation fail with the specified message, while traditional assert is meant to end the execution of your program.
A static assert declaration may appear at namespace and block scope (as a block declaration) and inside a class body (as a member declaration).
Of course, the expression in static assertion has to be a compile-time constant. It can't be a run-time value.
I agree with David Rodríguez - dribeas and in defense of compiler writers consider this example:
#include <type_traits>
class A {};
// I want the nice error message below in several functions.
// Instead of repeating myself, let's put it in a function.
template <typename U>
void check() {
static_assert(std::is_convertible<U*, const volatile A*>::value,
"U doesn't derive publicly from A "
"(did you forget to include it's header file?)");
}
template <typename U>
void f(U* u) {
// check legality (with a nice error message)
check<U>();
// before trying a failing initialization:
A* p = u;
}
class B; // I forget to include "B.h"
int main() {
B* b = nullptr;
f(b);
}
When the instantiation of f<B>
starts the compiler (or the compiler writter) might think: "Humm... I need to instantiate check<U>
and people always complain that compiling templates is too slow. So I'll keep going and perhaps there's something wrong below and I don't event need to instantiate check
."
I believe the reasoning above makes sense. (Notice that I'm not a compiler writter so I'm just speculating here).
Both GCC 4.8 and VS2010 keep compiling f<B>
, postponing the instantiation of check<B>
for later. Then they find the failing initialization and provide their own error messages. VS2010 stops immediately and I don't get my nice error message! GCC keeps going and yields the message that I wanted (but only after its own).
Metaprogramming is tricky for the programmers and for the compilers. static_assert
helps a lot but it's not a panacea.
There are multiple goals that need to be balanced here. In particular, smaller simpler error messages may be attained by stopping on the first error, which is good. At the same time stopping on the first error does not give you information about any other issues that you might want to solve before attempting another potentially expensive compilation. For example, in your first example I personally prefer all of the static_assert
s to be checked at once. Read the error message as:
You failed to meet the following requirements:
value
typeI'd rather have both those errors detected in the first pass, than fix one and have to way a few minutes for the build system to trip on the next one.
The premise here is that the compiler is able recover from the error and continue parsing, although the grammar is context dependent and that is not always the case, so part of the negative side of the problem is that you can trust the first error, but the next errors might be just a consequence of that first one, and it takes experience to realize which is which.
All this is quality of implementation (thus compiler dependent), and many implementations let you determine when to stop, so it is up to the user and the flags that are passed to the compiler. Compilers are getting better reporting errors and recovering from them, so you can expect improvements here. To further improve things, > C++14 (C++17? later?) will add concepts that are intended to improve the error messages.
Summarizing:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With