typedef changes meaning - c++

When I compile the following snippet with g++
template<class T>
class A
{};
template<class T>
class B
{
public:
typedef A<T> A;
};
the compiler tells me
error: declaration of ‘typedef class A<T> B<T>::A’
error: changes meaning of ‘A’ from ‘class A<T>’
On the other hand, if I change the typedef to
typedef ::A<T> A;
everything compiles fine with g++. Clang++ 3.1 doesn't care either way.
Why is this happening? And is the second behavior standard?

g++ is correct and conforming to the standard. From [3.3.7/1]:
A name N used in a class S shall refer to the same declaration in its
context and when re-evaluated in the completed scope of S. No
diagnostic is required for a violation of this rule.
Before the typedef, A referred to the ::A, however by using the typedef, you now make A refer to the typedef which is prohibited. However, since no diagnostic is required, clang is also standard conforming.
jogojapan's comment explains the reason for this rule.
Take the following change to your code:
template<class T>
class A
{};
template<class T>
class B
{
public:
A a; // <-- What "A" is this referring to?
typedef A<T> A;
};
Because of how class scope works, A a; becomes ambiguous.

I will add to Jesse's answer about the seemingly peculiar behavior of GCC in compiling:
typedef A<T> A;
vs
typedef ::A<T> A;
This also applies to using statements as well of the form:
using A = A<T>;
using A = ::A<T>;
What seems to be happening within GCC, is that during the compilation of the typedef/using statement declaring B::A, that the symbol B::A becomes a valid candidate within the using statement itself. I.e. when saying using A = A<T>; or typedef A<T> A; GCC considers both ::A and B::A valid candidates for A<T>.
This seems odd behavior because as your question implies, you don't expect the new alias A to become a valid candidate within the typedef itself, but as Jesse's answer also says, anything declared within a class becomes visible to everything else inside the class - and in this case apparently even the declaration itself. This type of behavior may be implemented this way to permit recursive type definitions.
The solution as you found is to specify for GCC precisely which A you're referring to within the typedef and then it no longer complains.

Related

Using declaration to abbreviate type of a template in c++ [duplicate]

When I compile the following snippet with g++
template<class T>
class A
{};
template<class T>
class B
{
public:
typedef A<T> A;
};
the compiler tells me
error: declaration of ‘typedef class A<T> B<T>::A’
error: changes meaning of ‘A’ from ‘class A<T>’
On the other hand, if I change the typedef to
typedef ::A<T> A;
everything compiles fine with g++. Clang++ 3.1 doesn't care either way.
Why is this happening? And is the second behavior standard?
g++ is correct and conforming to the standard. From [3.3.7/1]:
A name N used in a class S shall refer to the same declaration in its
context and when re-evaluated in the completed scope of S. No
diagnostic is required for a violation of this rule.
Before the typedef, A referred to the ::A, however by using the typedef, you now make A refer to the typedef which is prohibited. However, since no diagnostic is required, clang is also standard conforming.
jogojapan's comment explains the reason for this rule.
Take the following change to your code:
template<class T>
class A
{};
template<class T>
class B
{
public:
A a; // <-- What "A" is this referring to?
typedef A<T> A;
};
Because of how class scope works, A a; becomes ambiguous.
I will add to Jesse's answer about the seemingly peculiar behavior of GCC in compiling:
typedef A<T> A;
vs
typedef ::A<T> A;
This also applies to using statements as well of the form:
using A = A<T>;
using A = ::A<T>;
What seems to be happening within GCC, is that during the compilation of the typedef/using statement declaring B::A, that the symbol B::A becomes a valid candidate within the using statement itself. I.e. when saying using A = A<T>; or typedef A<T> A; GCC considers both ::A and B::A valid candidates for A<T>.
This seems odd behavior because as your question implies, you don't expect the new alias A to become a valid candidate within the typedef itself, but as Jesse's answer also says, anything declared within a class becomes visible to everything else inside the class - and in this case apparently even the declaration itself. This type of behavior may be implemented this way to permit recursive type definitions.
The solution as you found is to specify for GCC precisely which A you're referring to within the typedef and then it no longer complains.

Trying to understand templates and name lookup

I am trying to understand the following code snippets
Snippet #1
template <typename T>
struct A
{
static constexpr int VB = T::VD;
};
struct B : A<B>
{
};
Neither gcc9 nor clang9 throw an error here.
Q. Why does this code compile? Aren't we instantiating A<B> when inheriting from B? There is no VD in B, so shouldn't the compiler throw an error here?
Snippet #2
template <typename T>
struct A
{
static constexpr auto AB = T::AD; // <- No member named AD in B
};
struct B : A<B>
{
static constexpr auto AD = 0xD;
};
In this case, gcc9 compiles fine but clang9 throws an error saying "No member named AD in B".
Q. Why does it compile with gcc9/why doesn't it compile with clang9?
Snippet #3
template <typename T>
struct A
{
using TB = typename T::TD;
};
struct B : A<B>
{
using TD = int;
};
Here both clang9 and gcc9 throw an error. gcc9 says "invalid use of incomplete type 'struct B'".
Q. If struct B is incomplete here then why is it not incomplete in snippet #2?
Compiler flags used: -std=c++17 -O3 -Wall -Werror. Thanks in Advance!!!
I believe these essentially boil down to [temp.inst]/2 (emphasis mine):
The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, default arguments, or noexcept-specifiers of the class member functions, member classes, scoped member enumerations, static data members, member templates, and friends; […]
and [temp.inst]/9
An implementation shall not implicitly instantiate […] a static data member of a class template […] unless such instantiation is required.
The wording in the standard concerning implicit template instantiation leaves many details open to interpretation. In general, it would seem to me that you simply cannot rely on parts of a template not being instantiated unless the specification explicitly says so. Thus:
Snippet #1
Q. Why does this code compile? Aren't we instantiating A when inheriting from B? There is no VD in B, so shouldn't the compiler throw an error here?
You are instantiating A<B>. But instantiating A<B> only instantiates the declarations, not the definitions of its static data members. VB is never used in a way that would require a definition to exist. The compiler should accept this code.
Snippet #2
Q. Why does it compile with gcc9/why doesn't it compile with clang9?
As pointed out by Jarod42, the declaration of AB contains a placeholder type. It would seem to me that the wording of the standard is not really clear on what is supposed to happen here. Does the instantiation of the declaration of a static data member that contains a placeholder type trigger placeholder type deduction and, thus, constitute a use that requires the definition of the static data member? I can't find wording in the standard that would clearly say either yes or no to that. Thus, I would say that both interpretations are equally valid here and, thus, GCC and clang are both right…
Snippet #3
Q. If struct B is incomplete here then why is it not incomplete in snippet #2?
A class type is only complete at the point where you reach the closing } of the class-specifier [class.mem]/6. Thus, B is incomplete during the implicit instantiation of A<B> in all your snippets. It's just that this was irrelevant for Snippet #1. In Snippet #2, clang did give you an error No member named AD in B as a result. Similar to the case of Snippet #2, I can't find wording on when exactly member alias declarations would be instantiated. However, unlike for the definition of static data members, there is no wording in place to explicitly prevent the instantiation of member alias declarations during implicit instantiation of a class template. Thus, I would say that the behavior of both GCC and clang is a valid interpretation of the standard in this case…

Why are typedef templates illegal?

From a practical point of view, I understand that both typedef and test are somewhat "superfluous" and need to be removed if we want the following code to compile:
template< typename type_t >
typedef struct tagTest
{
int a;
} test;
However, I thought that the set of typedef declarations was a subset of the set of declarations. They just happened to have that specific decl-specifier. That was my rationalization for
typedef struct tagTest
{
int a;
} test;
introducing the identifier test and declaring the structure tagTest. If that interpretation is correct, then the following paragraph from the standard should allow template typedef's (although not with the meaning given by the keyword using).
The declaration in a template-declaration shall
—
(1.1)
declare or define a function, a class, or a variable, or
—
(1.2)
define a member function, a member class, a member enumeration, or a static data member of a class
template or of a class nested within a class template, or
—
(1.3)
define a member template of a class or class template, or
—
(1.4)
be an alias-declaration.
I cannot see error in my reasoning, yet the conclusion is illegal.
What are the relevant parts of the standard that solve the above conundrum?
UPDATE
Part of the above reasoning uses the fact that typedef struct declares a structure. The typedef specifier, as far as I understand it, implies that any variables declared are really types. That is, the typedef upgrades test from a mere variable to a type that is equivalent to the declared tagTest. That is why the following code compiles (albeit with a warning).
typedef struct tagTest
{
int a;
};
tagTest t;
One of the answers takes care of the superfluous test. But, it is possible to use typedef without a declarator because "Init-declarator-list is optional when declaring a named class/struct/union or a named enumeration"
Template typedefs weren't allowed pre-C++11 and with C++11 template aliases were introduced to address those issues. Cfr. C++ template typedefs and wikipedia.
Since, as you noted, the standard doesn't allow typedef to be in there, the code is invalid
alias-declaration:
using identifier attribute-specifier-seqopt= type-id ;
typedef declarations are not alias declarations.
Furthermore you can't have a declarator if you're declaring a class template, it is explicitly forbidden by the standard
[temp]/p3
In a template-declaration, explicit specialization, or explicit instantiation the init-declarator-list in the declaration
shall contain at most one declarator. When such a declaration is used to declare a class template,
no declarator is permitted.
so not even the following will compile
template< typename type_t >
struct tagTest
{
int a;
} test;
Edit:
It is nowhere specified that
typedef struct S { };
should be an error, thus both gcc and clang accept it with a warning. I assume Clang counts on [temp]/3 to issue an error in case typedef was being used with a template while gcc rejects this code immediately
template<typename T>
typedef struct S { };
cfr. clang bug 22249
Independently from what the typedef defines, that is a typedef declaration, which is not listed in those cases:
[member] function
[member] class
variable
member enumeration
static data member of a class template/of a class nested within a class template
member template of a class or class template
alias declaration
And just to be clear typedef declarations are not alias declarations. Alias declaration, as specified by the grammar at §7 of the standard are:
alias-declaration:
using identifier attribute-specifier-seqopt= type-id ;
Not to mention that if this was possible, then template using declaration would not be nearly as "cool" as they are today, and there would be little to no sense to have both.
C does not support templates and the
typedef struct tagX {
} X;
syntax in C++ is vestigial C, there to allow continued support for C headers etc, not for use in actual C++.
The C++ syntax for the above is
struct X {};
(YMMV on brace placement)

C++ template definition fails on clang, works on GCC

The following code compiles on GCC (doesn't even require a recent version), but it fails on clang 3.4:
template <int N>
struct T_unsigned {
typedef typename T_unsigned<N>::Type Type;
};
template <> struct T_unsigned<8> {
typedef unsigned char Type;
};
Using the above definition of T_unsigned, GCC allows you to use "T_unsigned<8>::Type" instead of "unsigned char". When I try to compile this using clang 3.4, I get:
test.cpp:3:41: error: no type named 'Type' in 'T_unsigned<N>'
typedef typename T_unsigned<N>::Type Type;
~~~~~~~~~~~~~~~~~~~~~~~~^~~~
1 error generated.
Is clang failing to compile correct C++11 code, or is this code doing something non-standard that GCC happens to support?
In your general case T_unsigned<N>, we have:
A name is a member of the current instantiation if it is
[...]
A qualified-id in which the nested-name-specifier refers to the current instantiation.
[ Example:
template <class T> class A {
static const int i = 5;
int n1[i]; // i refers to a member of the current instantiation
int n2[A::i]; // A::i refers to a member of the current instantiation
int n3[A<T>::i]; // A<T>::i refers to a member of the current instantiation
int f();
};
Within T_unsigned<N>, T_unsigned<N> is just another name for itself. So basically you have something like:
class Foo {
typedef Foo::Type Type;
};
and the "correct" error message should be approximately (http://ideone.com/FvJHBF):
prog.cpp:2:17: error: ‘Type’ in ‘class Foo’ does not name a type
typedef Foo::Type Type;
^
However, you write that you have problems with using your specialization T_unsigned<8>, which is seemingly not found by clang.
Your testcase is not too exhaustive, so my answer depends on an if-statement:
If at the point of instantiation, your specialization for N=8 is visible, than clang is doubly wrong. If not, gcc and clang should both fail, but with aforementioned error message (though the error message itself is in no way defined by the standard, so this is a tool's should, not a standard's).
The name that you use as the typedef is not dependent (see the clauses that define dependent names) and the namelookup in definition context at that place will not find a declaration. That in itself is an error already.
But since there is no declaration for the name yet, the name is also not a member of a class that is the current instantiation or a base class thereof. Therefore the name is not a member of the current instantiation and therefore we have another reason to reject it by a rule that says that if the qualifier is the current instantiation, the name either must refer to a member of that instantiation, or to a member of an unknown specialization (which would be the case if the class had dependent base classes).
Note the notion of "current instantiation": the meaning of the qualifier is fixed to refer to the result of instantiating the surrounding template, we don't wait for resolving the template arguments. Hence the term is not called "current specialization", since we know that it refers to an instantiated specialization, as opposed to a later declared explicit specialization.
The thing is different for C++03 I think. The name will be dependent and the template definition is harder to deem illformed with the rules available. The illformed, no diagnostic required, behavior will however happen when you try to instantiate the template before providing the explicit specialization. I think such code refering to itself makes no sense because you never are able to actually instantiate the template validly (and there is a rule that allows rejecting a template straight away in such cases).

Ambiguous injected class name is not an error

What I read in the C++ standard about injected class names contradicts (as I see it) with the behavior of a sample program I will present shortly. Here's what I read:
From 3.4 (paragraph 3)
The injected-class-name of a class (clause 9) is also considered to be
a member of that class for the purposes of name hiding and lookup.
From 9 (paragraph 2)
A class-name is inserted into the scope in which it is declared
immediately after the class-name is seen. The class-name is also
inserted into the scope of the class itself; this is known as the
injected-class-name. For purposes of access checking, the
injected-class-name is treated as if it were a public member name.
From these I understand that the following is a well-formed translation unit and it compiles successfully.
#include <vector>
class X: std::vector<int>
{
vector mem;
};
However, I would suppose that the following should have produced an error, but it doesn't
#include <vector>
class X: std::vector<int>, std::vector<char>
{
vector mem; //compiles OK... mem is apparently std::vector<int>
};
Since the name vector is injected into both std::vector<int> and std::vector<char> as as if a public member name, then it should be inherited by X and therefore the name vector in X should be ambiguous. Am I missing something?
P.S. I am using MSVC9.0
I found it! It's right there in the standard! I was right! It should be ambiguous!
Clause 14.6.1 Paragraph
A lookup that finds an injected-class-name (10.2) can result in an
ambiguity in certain cases (for example, if it is found in more than
one base class). If all of the injected-class-names that are found
refer to specializations of the same class template, and if the name
is followed by a template-argument-list, the reference refers to the
class template itself and not a specialization thereof, and is not
ambiguous. [Example:
template <class T> struct Base { };
template <class T> struct Derived: Base<int>, Base<char>
{
typename Derived::Base b; // error: ambiguous typename
Derived::Base<double> d; // OK
};
—end example]
Bottom line: This is yet another Microsoft compiler BUG. Disabling language extensions doesn't help either.
No, you are not missing anything, and your compiler seems to behave buggy. You can see how gcc handles it here: http://ideone.com/MI9gz
Its error message is:
prog.cpp:4:4: error: reference to 'vector' is ambiguous
/usr/lib/gcc/i686-pc-linux-gnu/4.5.1/../../../../include/c++/4.5.1/bits/stl_vector.h:171:5: error: candidates are: class std::vector<char> std::vector<char>::vector
/usr/lib/gcc/i686-pc-linux-gnu/4.5.1/../../../../include/c++/4.5.1/bits/stl_vector.h:171:5: error: class std::vector<int> std::vector<int>::vector