For a few days I've been using and testing my application without any trouble using the following code:
class dataHandler
{
public:
template<class T>
T GetData(bool truncate = false) { static_assert(false, "Unhandled output type"); }
template<T>
int GetData<int>(bool truncate)
{
// Normal stuff that compiles
}
}
This (As I expected at the time) works fine as long as no implicit instantiation of GetData's default is performed. However, today after adding a new failing specialisation for void* specifically (I wanted a different error) I discovered it wouldn't compile due to the assertion, even though the `void* spec was never called or mentioned in code.
I started a new C++ test project (with the same C++ version, MSVC version, and VS2022 version) and found this also doesn't compile for the same reason:
template<class T>
T Spec()
{
static_assert(false, "default");
}
template<>
int Spec<int>()
{
return 1;
}
I was unable to replicate the original 'success' in anything I tried within the test project, and the failing GetData<void*> assert in the initial project seems to indicate that it's not a project config / difference in toolset issue.
After some searching I discovered that it failing was the intended (or otherwise expected) behaviour, as well as a workaround for it.
However I find myself wondering why the initial case of static_assert(false) didn't also fail to compile. Is this some niche exception, or is this an issue with MSVC's consistency?
It's certainly not an inconsistency. The key part here is "Standard conformance mode", controllable with the compiler option
/permissive-. See the documentation of this compiler option.The reason that MSVC from before VS2017 used to accept your
static_assert(false, ...), is because it postponed parsing the contents of a function template until template instantiation. That is just how the parser used to work, and also why features like two-phase name lookup could never be properly implemented. I'll refer you to this blog post for more background on this.You can easily try it for yourself. Even if the function contains ill-formed code that shouldn't even parse correctly, the compiler doesn't complain as long as the template isn't being instantiated:
It appears to do some basic paranthesis matching and that is it. The token stream is recorded and parsed when
foois instantiated (usually when called).In order to fix two-phase lookup (and other conformance issues), they had to fix the compiler and parse the code whenever a function template is encountered, rather than being instantiated. But now they have a problem, because a lot of old code might be reliant on the old compiler behavior that suddenly doesn't compile anymore. So they introduced the
/permissive-option for users to opt-in into standard conformance mode. And gradually the default was changed for new projects or certain compiler options, as can be read in the documentation:Which brings us to the answer to your question. You weren't seeing it in your original code because you were compiling without
/permissive-. In your test project, created in VS2022,/permissive-mode was set by default so it failed to compile. It also fails to compile in an explicit specialization (yourvoid*case) because at that point the template arguments are known and the function is instantiated.There are a couple of ways to properly fix your code. One is by explicitely deleting the main template and any specialiation you don't want to have.
This will make any use of the explicitely deleted variants ill-formed, without having the option to include an error message. Of course the
void*case is a bit redundant in this example.If you want to stick to
static_assert, you must make the condition for the assert dependent on the template arguments. If you are compiling with C++17, you could do:If using C++14 or earlier, you could wrap the
always_falseinto a struct template: