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
Related
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");
}
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 was playing with C++17's class template argument deduction today. The first obvious idea that came to mind was passing a callable as template parameter. A callable, that's among other things a lambda, why not. Let's try that.
template<typename F> class foo
{
F f;
public:
foo(F in) : f(in) { f(); /* not very useful, admitted */ }
};
void bar() { puts("a"); }
int main()
{
auto a = foo(bar);
auto b = foo([](){ puts("b"); });
return (void) a, (void) b, 0;
}
Here is what clang (5.0, r300688) has to say about it:
warning: function '<(lambda at [source
location])>' has internal linkage but is not defined
The code compiles and certainly "works fine" but the warning suggests the compiler is not altogether happy with it.
I'm ready to agree that the lambda has internal linkage (being anonymous it's not accessible elsewhere in the same translation unit, so sure enough it's inaccessible in another one), but what about it. I don't want to access it from another translation unit.
The part about lacking definition strikes me as funny, I wouldn't even know how to write a lambda without defining it.
In summary: What gives? What to make of that? I don't like warnings, not only do they make the build less pretty, but they usually mean something is wrong and undefined behavior of sorts may bite you soon. On the other hand, how can I make a lambda more defined than it already is by writing out its definition?
It seems to me this is a compiler glitch. Using Clang compiler of Visual Studio 2017 only this error is generated "cannot refer to class template 'foo' without a template argument list" for a and b instantiation in main function. If the function type is specified as template parameter, there are no warnings and no errors.
Recently I ran into a few nonsense warnings that looked, and smelled, similar to yours. I tried a bunch of hand-wavey fixes with the idea that I could force the linker’s hand (if you will) – but at the end of the day I realized that I was trying to compile C++14 code with a -std=c++17 flag. You may want to double-check your own standards flags (and possibly your corresponding -stdlib=… flags as well).
I have problem with following code:
template <typename T>
void foo(struct bar & b);
struct bar {};
int main(){}
It compiles successfuly on GCC, but fails on MSVC (2008) with following error:
C2990: 'bar' : non-class type as already been declared as a class type
Is the code wrong or it's a bug in MSVC?
It works if I add struct bar; before template definition.
And we have our winner:
https://connect.microsoft.com/VisualStudio/feedback/details/668430/forward-declared-type-and-non-class-type-as-already-been-declared-as-a-class-type
Thank you for reporting this issue.
This is indeed a case of
non-conformant behaviour in VC++.
However, a simple workaround is to
reorder the declarations so that the
declaration "struct bar" is known when
the template declaration is
encountered. Due to the low severity
of this bug and our priorities, we
regret that we cannot fix the bug in
the next release of the compiler but
we will consider it for a future
release.
Regards,
Tanveer Gani
Visual C++ Team
In most situations, a C (or C++ compiler) works strictly top-to-bottom on your source code. So you need a forward declaration before you ever attempt to reference struct bar, otherwise the compiler will not know that it exists.
Anyway, I've posted a bug in Microsoft Connect: https://connect.microsoft.com/VisualStudio/feedback/details/668430/forward-declared-type-and-non-class-type-as-already-been-declared-as-a-class-type
You most likely have struct bar {}; somewhere above this block of code (possibly in a header file). See http://msdn.microsoft.com/en-us/library/zfcw8kk9.aspx
Edit: Also from the link above:
C2990 can also occur due to a breaking
change in the Visual C++ compiler for
Visual C++ 2005; the compiler now
requires that multiple declarations
for the same type be identical with
respect to template specification.
Since foo is templated and bar is being "forward-declared" in the foo argument list, what happens if you move struct bar {}; above foo?
That looks like valid code. Whatever MSVC is doing, it appears to be some weird non-conforming behavior, from what I can see.