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?
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:
template<class T>
void foo()
{
Normally this should not (never actually (compile)) but it does anyway
}
It appears to do some basic paranthesis matching and that is it. The token stream is recorded and parsed when foo is 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:
The /permissive- option is implicitly set by the /std:c++latest option starting in Visual Studio 2019 version 16.8, and in version 16.11 by the /std:c++20 option. /permissive- is required for C++20 Modules support. Perhaps your code doesn't need modules support but requires other features enabled under /std:c++20 or /std:c++latest. You can explicitly enable Microsoft extension support by using the /permissive option without the trailing dash. The /permissive option must come after any option that sets /permissive- implicitly.
By default, the /permissive- option is set in new projects created by Visual Studio 2017 version 15.5 and later versions. It's not set by default in earlier versions. When the option is set, the compiler generates diagnostic errors or warnings when non-standard language constructs are detected in your code. These constructs include some common bugs in pre-C++11 code.
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 (your void* 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.
template<class T> void foo() = delete;
template<> void foo<void*>() = delete;
template<> void foo<int>()
{
// ...
}
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:
template<class...> constexpr bool always_false = false;
template<class T> void foo()
{
static_assert(always_false<T>, "your error here");
}
If using C++14 or earlier, you could wrap the always_false into a struct template:
#include <type_traits>
template<class...> struct always_false : false_type { };
template<class T> void foo()
{
static_assert(always_false<T>::value, "your error here");
}
Related
Using the following declarations:
#include <array>
template <typename V> void makeError(V& v) {}
the following code snippet fails to compile under MSVC 16.11.13 with /std:c++17:
int main() {
[&]() {
std::array bugs{0};
[&]() { makeError(bugs); };
};
return 0;
}
The error message is as follows: main.cpp(9,5): error C2955: 'std::array': use of class template requires template argument list
The same code compiles perfectly fine under GCC 10.4 with --std=c++17. The real problem came up when I tried removing the inner closure:
int main() {
[&]() {
std::array bugs{0};
makeError(bugs);
};
return 0;
}
Or the outer closure:
int main() {
std::array bugs{0};
[&]() { makeError(bugs); };
return 0;
}
Both of these compile happily under MSVC and GCC.
Is the first snippet invalid C++? If so, what part of the standard am I violating?
Looks like this is a bug in msvc's legacy lambda processor. Your code compiles if you pass /Zc:lambda: https://godbolt.org/z/91z4chhTx
This seems to be the matching bug report: https://developercommunity.visualstudio.com/t/class-template-argument-deduction-fails-in-the-bod-1/414204
How I found this
I would always recommend using godbolt in cases like this, since it allows you to quickly switch between compilers, versions and command line args.
If something smells like a compiler bug, checking the latest versions is always a good idea. If something compiles under gcc & clang, but not msvc, it smells like a compiler bug.
I tried your code in the latest msvc and since it involves lambdas and captures, I remembered that there were changes to that in c++20, so I tried compiling as such. Since that worked, I had look through cppreference to see if I could find any relevant change to the standard that might legitimately cause this. I didn't find any, but then remembered that msvc has also constantly be increasing its standards conformance over the recent years.
So I used /permissive- with c++17 and once I noticed that this builds, I had a look through the conformance flags and found /Zc:lambda.
This confirmed this as a fixed compiler bug to me. And fixed bugs often have bug reports, so I googled the keywords that seemed relevant to the situation:
msvc nested lambdas template deduction. The bug report is the second result for me.
My understanding of function templates has always been: if they contain invalid
C++ and you don't instanciate them, your project will compile fine.
However, the following code:
#include <cstdio>
#include <utility>
template <typename T>
void contains_compile_time_error(T&& t) {
int j = nullptr;
}
int main() {}
Compiles with:
x86-64 gcc 11.2 and flag -std=c++20;
x64 msvc v19.31 and flag /std:c++20;
and does not with x86-64 clang 14.0.0 (with flags -std=c++{11,14,17,20}):
error: cannot initialize a variable of type 'int' with an rvalue of type 'std::nullptr_t'
int j = nullptr;
^ ~~~~~~~
1 error generated.
Even more confusing to me, the following code:
#include <cstdio>
#include <utility>
namespace ns {
struct S {};
template <typename T>
void apply(T&& t) {
//print(std::forward<T>(t));
ns::print(std::forward<T>(t));
}
void print(S const&) { std::puts("S"); }
} // namespace ns
int main() {}
Does not compile with:
x86-64 gcc 11.2 and flag -std=c++20;
x64 msvc v19.31 and flag /std:c++20;
x86-64 clang 14.0.0 and flags -std=c++{11,14,17,20};
(all of them complaining that print is not a member of 'ns') but does compile with x64 msvc v19.31 /std:c++17.
If I call unqualified print, then the code compiles with all the above compilers.
So, my questions are:
is my understanding of function templates wrong?
why do the above compilers behave differently with the code snippets I posted?
Edit 0: as per Frank's comment, x64 msvc v19.31 /std:c++17 /permissive- fails to compile function template apply where I call the qualified ns::print.
My understanding of function templates has always been: if they contain invalid C++ and you don't instanciate them, your project will compile fine.
Nope, if the code in the template is invalid, then so is the program.
But what does "invalid C++" mean? You can have syntactically valid C++ that is still invalid semantically, and the semantics of C++ are highly contextual.
So there's multiple levels of "valid" that can be checked at different times during compilation, based on the available information. Critically, there are things that can theoretically be checked early, but are potentially unreasonably difficult. Because of this, compilers are allowed some leeway when it comes to validating the semantics of template definitions. However, whether the code is broken or is not ambiguous, it's a compiler's ability to detect broken code that is.
why do the above compilers behave differently with the code snippets I posted?
As far as int j = nullptr is concerned:
The program is technically ill-formed, but the diagnostics is optional. So The code is broken, but GCC is not breaking compliance by letting it through.
For example, if S only had private constructors, then print(std::forward<T>(t)) would be flaggable as being broken since there is no possibly T that would make it valid. However, requiring compilers to be smart enough to determine this in all cases would be kind of mean.
For print():
Lookups are a bit of a different matter, there are hard rules that are supposed to be followed.
There are three types of lookups involved here.
Qualified lookups, such as ns::print
Unqualified lookups not involving template parameters, which you'd get if you tried print(S{});
Unqualified lookups involving template parameters, such as print(std::forward<T>(t));
Lookups are deferred for the third category, but must still be performed the same as non-template functions for the other two.
Note that the code still needs to be syntactically valid, even in the case of deferred lookups, hence why typename needs to be added when doing a dependent lookup of a type.
And as for MSVC allowing ns::print: That compiler is not compliant by default on this (and other) aspect of the standard. You need to use the /permissive- compiler flag to enforce compliance.
Is there a Visual Studio (2019) build setting I can change to make the first line below fail to compile when I neglect to put the typename keyword. Because GCC keeps complaining that it's required and won't build without it.
auto v = std::dynamic_pointer_cast<T::element_type>(sp); // Builds in VS, fails in GCC
auto v = std::dynamic_pointer_cast<typename T::element_type>(sp);// Builds in both VS and GCC
Note:
sp is shared_ptr<Base>
T is shared_ptr<Derived>
Derived derives from Base
I've already got the VS language standard set to C++17 (/std:c++17) the warning level set to 4 (/W4) and full compatibility turned on (/permissive-). But Visual Studio still lets that line pass. GCC (version 10.3 set to build as C++17) will not compile due to the missing typename
I don't care which compiler is right about the keyword still being required. The point is that it's legal, whether it's superfluous or not. I just want to force them to agree on the topic via build switches if possible.
So alternately, if that keyword is no longer truly required according to the C++ 17 standard going forward, then is there GCC build setting I can flip to allow the line of code to pass without the keyword?
[EDIT]
I'm adding a minimal reproducible example here. Apologies for not putting it in initially but I was mostly asking for a compiler flag or something so I figured it wasn't necessary. The following code builds just fine on VS 2019 16.11.3 with the compiler settings I mentioned. It is admittedly contrived but I adapted it from the actual code which is pretty hairy...
#include <stdexcept>
#include <type_traits>
#include <memory>
class Base
{
public :
Base() {}
virtual ~Base() {}
};
using BasePtr = std::shared_ptr<Base>;
class Derived : public Base
{
};
using DerivedPtr = std::shared_ptr<Derived>;
template<class T>
typename std::enable_if<std::is_convertible<T, BasePtr>::value, T>::type
getAsT(BasePtr sp)
{
const auto out = std::dynamic_pointer_cast<T::element_type>(sp);
if (nullptr == out)
throw std::runtime_error("Not the right type");
return out;
}
int main()
{
auto derived = std::make_shared<Derived>();
auto derived2 = getAsT<DerivedPtr>(derived);
}
I do not have a copy of Visual Studio to hand to experiment with, so I'm going to make some generic suggestions. (don't shout at me, I'm trying to be helpful)
Firstly, this might not be something the compiler should deal with. Language checks can be done with a static analyzer. Visual Studio has a built-in static analyzer. You could try turning it on and searching for rules related to "typename" follow the instructions here
A similar technique that will definitely work is to install a "clag-tidy" plug-in for visual studio, of which there are a few to choose from. In general, if clang-tidy can parse your code, GCC will work too.
You can often map clang-tidy to run on a key press, or even on saving a file.
Sorry if this is not specific enough, but I hope it gives you a few options.
I just happened to find that a nested private template class can be accessed directly outside the enclosing class using a using directive:
class wrapper
{
private:
template <typename T>
class __tklass {};
class __klass {};
};
template <typename T>
using tklass = wrapper::__tklass<T>; // Expected error but compiles OK
// using klass = wrapper::__klass; // "Error: __klass is private"
int main()
{
tklass<int> v1; // Expected error but compiles OK
// wrapper::__tklass<int> v3; // "Error: __tklass is private"
// wrapper::__klass v4; // "Error: __klass is private"
}
The lines marked "Error: __xxx is private" correctly report an error when uncommented. But the lines with tklass get compiled without any complaint from the compiler.
So why exactly doesn't the compiler flag tklass as error despite wrapper::__tklass being private? Is it by any chance allowed by the standard? If so, wouldn't that be considered a serious access violation?
I tried this on gcc-4.9.2, clang-3.5.0 and visual studio 2013 express. GCC command line:
g++ -std=c++11 -pedantic -Wall -Wextra -Wshadow myfile.cpp
This is definitely a compiler bug, and actually one that has been known for quite some time: GCC #47346 (first reported in Jan 2011) and Clang #15914 (first reported May 2013). Your __tklass is clearly private, and the template alias is not marked friend, so this should be a simple access error.
The simplest reproduction is from the Clang example attachment, this version compiles on both gcc 4.9.2 and clang 3.5.0, though should definitely compile on neither:
class A
{
class B {};
};
template<typename>
using T = A::B;
T<void> t;
Clang is strictly better than GCC on this front however, as this particular bug seems to occur only with template aliases. A "workaround" (if you need such a thing for cases that the compiler allows incorrectly...) would be to revert back to pre-C++11 template aliasing:
template <typename>
struct T {
using type = A::B;
};
T<void>::type t;
That code correctly fails to compile with clang (error: 'B' is a private member of 'A'), but still compiles fine with gcc.
Herb Sutter wrote long ago the article, how template member functions may provide a back-door into a class:
http://www.gotw.ca/gotw/076.htm (pls. check the case 4: "The Language Lawyer", at the end)
It may give you the answer.
EDIT: I'm curious, what were the reasons for down-voting. I cite the article:
"Is this a hole in C++'s access control mechanism, and therefore a hole in C++'s encapsulation?
This demonstrates an interesting interaction between two C++ features: The access control model, and the template model. It turns out that member templates appear to implicitly "break encapsulation" in the sense that they effectively provide a portable way to bypass the class access control mechanism."
Seems for me to be a reasonable answer.
EDIT END
One could spent plenty of time trying to secure the interfaces by technical means private/protected, etc. My preferred way is to make an agreement among all developers to use well, understood rules complying with least surprise approach. (EDIT: And verify the code against these rules using the code-reviews/reg-exp scripts on regular basis)
"Don't try to find a technical solution for a social problem" B. Stroustrup
I have a piece of code that pretty much reduces down to:
template<class T> struct MyStruct; // No definition by default
template<class T> struct MyStruct<T *> { ... }; // Specialization for pointers
Now somewhere in my code, I'm getting an instantiation of MyStruct<T> that happens to be undefined (no C++0x/011, no Boost... nothing fancy, just plain C++03):
error C2027: use of undefined type 'MyStruct<T>'
The trouble is, I have no idea where this is being caused, because the code that's doing the instantiation is itself a template, and called from numerous places, with different arguments.
Is there a way to somehow figure out what T is at compile-time, so I can understand the error messages better?
(Sorry, I forgot to mention: Visual Studio 2008.)
I believe you're using MSVC++, if so, then see the output window, it might have more info printed, especially the line number along with the filename. Once you know the file and line number, you can start from there.
Output window usually prints everything, like how and with what template argument(s), a template is instantiated. Everything step by step. Those messages are very useful when debugging.
As you found yourself, enabling /WL prints more detail messages in the output window.
I know you said no C++11, but you may want to consider, since C++03 code is backwards compatible in all C++11 compliant compilers, to use the static_assert feature of C++11 to debug your code ... if you must do the final compile with a C++03 compiler, then you can always create a #define and use the #ifdef and #endif pre-processor macros to make sure that the static_assert feature does not cause problems in earlier compilers that do not support C++11 features.
See the MSDN docs here for more info.