Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Out-of-class definition of constructor for template class

1 view
Skip to first unread message

Andrey Tarasevich

unread,
Nov 10, 2009, 4:26:19 PM11/10/09
to
Hello

Consider the following simple example

template <typename T> struct S {
S();
};

template <typename T> S<T>::S() {
}

Note, that the class name in out-of-class constructor definition
includes the template parameter `S<T>`, as it should, but the
"constructor name" (which is actually the class name again) uses the
short form `S`, without the `<>` part. This is perfectly legal in C++.

Now, let's try the "longer" form of the same definition

template <typename T> S<T>::S<T>() {
}

Personally, I always used the former "short" form, all the while
believing that the "longer" variant is also legal. Indeed, GCC compiler
(for one example) accepts both. But a couple of days ago I suddenly
discovered, to my great surprise, that Comeau Online compiler rejects
the second variant!

The error message says

"ComeauTest.c", line 5: error: overloaded function "S<T>::S" is not a
template,
Should it be XX::overloaded function "S<T>::S"?, where XX is some
namespace?
Did you #include the right header?
template <typename T> S<T>::S<T>() {
^

The "namespace" and "right header" comments embedded into the error
message make no sense, but the "not a template" bit does make sense, in
a way. Apparently, the compiler believes that the `<>` in the second
part of the name is only allowed when the constructor itself is a
template (i.e. a template constructor in a template class).

Again, this logic does make certain informal sense to me, but I still
tend to believe that this is a bug in Comeau compiler. So I would like
to ask it here: is there something I missed in the langiage
specification that would outlaw the latter, "longer" definition in the
above example?

--
Best regards,
Andrey Tarasevich

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Johannes Schaub (litb)

unread,
Nov 11, 2009, 12:16:36 AM11/11/09
to
Andrey Tarasevich wrote:

> Hello
>
> Consider the following simple example
>
> template <typename T> struct S {
> S();
> };
>
> template <typename T> S<T>::S() {
> }
>
> Note, that the class name in out-of-class constructor definition
> includes the template parameter `S<T>`, as it should, but the
> "constructor name" (which is actually the class name again) uses the
> short form `S`, without the `<>` part. This is perfectly legal in C++.
>

If you use an unqualified-id in a declaration, this is a new declaration of
something which might also be a re-declaration. But if you use a qualified-
id, this is always a re-declaration: First, the entity specified in the
qualified-id are looked up (to see whether it's already been declared), and
then whatever the declaration specifies is considered. (8.3/1).

If you declare/define an in-class constructor, you use a special notation,
in which the declarator-id is the constructor's class name. Both "S<T>" and
"S" are class-names for its class (14.2/6). So you may use a template-id in
an in-class declaration of a constructor:

template<typename T> struct S { S<T>() { } /* fine */ };


> Now, let's try the "longer" form of the same definition
>
> template <typename T> S<T>::S<T>() {
> }
>
> Personally, I always used the former "short" form, all the while
> believing that the "longer" variant is also legal. Indeed, GCC compiler
> (for one example) accepts both. But a couple of days ago I suddenly
> discovered, to my great surprise, that Comeau Online compiler rejects
> the second variant!
>

If you use an out-of-class definition, the declaration must use a qualified-
id, and lookup has to resolve to the constructor, because that's what you
want to declare. 3.4.3.1/1a specifies how this is done:

"If the nested-name-specifier nominates a class C, and the name specified
after the nested-name-specifier, when looked up in C, is the injected-class-
name of C (clause 9), the name is instead considered to name the constructor
of class C. Such a constructor name shall be used only in the declarator-id
of a constructor definition that appears outside of the class definition."

The injected-class-name is an identifier that refers to its class. For
templates, the injected-class-name may be followed by a template-argument-
list, but 3.4.3.1/1a does not say that this form would refer to the
constructor too. Instead, it would refer to the class itself - this is what
breaks the declaration above, i think.

--

Johannes Schaub (litb)

unread,
Nov 11, 2009, 7:59:12 PM11/11/09
to
Johannes Schaub (litb) wrote:

> Andrey Tarasevich wrote:
>> Now, let's try the "longer" form of the same definition
>>
>> template <typename T> S<T>::S<T>() {
>> }
>>
>> Personally, I always used the former "short" form, all the while
>> believing that the "longer" variant is also legal. Indeed, GCC compiler
>> (for one example) accepts both. But a couple of days ago I suddenly
>> discovered, to my great surprise, that Comeau Online compiler rejects
>> the second variant!
>>
> If you use an out-of-class definition, the declaration must use a
> qualified- id, and lookup has to resolve to the constructor, because
> that's what you want to declare. 3.4.3.1/1a specifies how this is done:
>
> "If the nested-name-specifier nominates a class C, and the name specified
> after the nested-name-specifier, when looked up in C, is the
> injected-class- name of C (clause 9), the name is instead considered to
> name the constructor of class C. Such a constructor name shall be used
> only in the declarator-id of a constructor definition that appears outside
> of the class definition."
>
> The injected-class-name is an identifier that refers to its class. For
> templates, the injected-class-name may be followed by a template-argument-
> list, but 3.4.3.1/1a does not say that this form would refer to the
> constructor too. Instead, it would refer to the class itself - this is
> what breaks the declaration above, i think.
>

I've rethought this, and wondered about this one:

template<typename T>
class F { template<typename U> F(U); };

template<> template<>
F<int>::F<int>(int);

If my above analysis is correct, then that code should fail, because
"F<int>::F<int>" names the class type, not the constructor. But apparently
compilers disagree:

- Comeau/EDG makes it refer to the constructor
- GCC makes it refer to the class and thus issues an error. (clang agrees
with GCC here, but then clang is still under heavy development currently)

I'm not sure which compiler is correct - for both, i can find some support:

If i agree with EDG: Because of the way a template-id is associated with a
type/function: In the qualified-id, the name after the nested-name-specifier
is "F<int>". This names a specialization, and 14.2 seems to indicate that
name-lookup only cares about the template-name, and not about the argument-
list:

"[Note: the name lookup rules (3.4) are used to associate the use of a name
with a template declaration; that is, to identify a name as a template-name.
]"

"After name lookup (3.4) finds that a name is a template-name, if this name
is followed by a <"...

So, this is what might happen in your case:

"S<int>::S" is looked up, and "S" names the constructor[s]. Then "<int>" is
taken to do argument deduction on a function template, but there is no
constructor template in the lookup set (this would make sense given the
error message).

In my case of the explicit specialization, "F<int>::F" is looked up (even
tho the "name after the nested-name-specifier" is "F<int>", but since it's a
template-id, only the template-name may be of interest), is looked up to the
injected-class-name, thus referring to the constructor[s]. Argument
deduction substitutes explicitly given arguments and declares the
specialization.

---

If i agree with GCC: The name is looked-up (it's associated with a
declaration): "F<int>" refers to the class, but not the the injected-class-
name. There is no clear statement that name-lookup is only about the
template-name - but 14.2 says that name-lookup resolves the template-name.
That doesn't mean that name-lookup doesn't also care about the whole
template-id. In fact, it has to, because how else could it resolve something
like "F<int>::foo" then (the first part is a template-id)? And 3.4 says at
the very beginning:

"The name lookup rules apply uniformly to all names (including typedef-names
(7.1.3), namespace-names (7.3) and class-names (9.1)) wherever the grammar
allows such names in the context discussed by a particular rule." - name
lookup so applies also to template specialization names.

Nikolay Ivchenkov

unread,
Nov 11, 2009, 8:22:51 PM11/11/09
to
On 11 Nov, 08:16, "Johannes Schaub (litb)" <schaub-johan...@web.de>
wrote:

> If you use an unqualified-id in a declaration, this is a new declaration of
> something which might also be a re-declaration. But if you use a qualified-
> id, this is always a re-declaration: First, the entity specified in the
> qualified-id are looked up (to see whether it's already been declared), and
> then whatever the declaration specifies is considered. (8.3/1).

The constructor's declarator is always neither unqualified-id nor
qualified-id. Its declarator-id has the form ":: opt nested-name-
specifier opt type-name" where type-name is the constructor's class
name.

> The injected-class-name is an identifier that refers to its class. For


> templates, the injected-class-name may be followed by a template-argument-
> list, but 3.4.3.1/1a does not say that this form would refer to the
> constructor too.

According to 3.4.3.1/1a, S<T>::S is treated as constructor's
declarator-id, so S<T>::S and S<T>::S<T> are not considered to be
injected-class-name. The constructor's declarator followed by <T> is
illegal here. See also issue 147:
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#147

Johannes Schaub (litb)

unread,
Nov 12, 2009, 3:43:38 AM11/12/09
to
Nikolay Ivchenkov wrote:

> On 11 Nov, 08:16, "Johannes Schaub (litb)" <schaub-johan...@web.de>
> wrote:
>> If you use an unqualified-id in a declaration, this is a new declaration
>> of something which might also be a re-declaration. But if you use a
>> qualified- id, this is always a re-declaration: First, the entity
>> specified in the qualified-id are looked up (to see whether it's already
>> been declared), and then whatever the declaration specifies is
>> considered. (8.3/1).
>
> The constructor's declarator is always neither unqualified-id nor
> qualified-id. Its declarator-id has the form ":: opt nested-name-
> specifier opt type-name" where type-name is the constructor's class
> name.
>

I should have been more specific (i tend to write too much nonsense): I was
talking about declarations in general, where the declarator-id is an id-
expression - to make my point that for an in-class constructor declaration
that uses a class-name, no qualified name-lookup occurs for the declarator-
id and so a "S<T>" as a class-name suffices (according to 12.1/1).

>> The injected-class-name is an identifier that refers to its class. For
>> templates, the injected-class-name may be followed by a
>> template-argument- list, but 3.4.3.1/1a does not say that this form would
>> refer to the constructor too.
>
> According to 3.4.3.1/1a, S<T>::S is treated as constructor's
> declarator-id, so S<T>::S and S<T>::S<T> are not considered to be
> injected-class-name. The constructor's declarator followed by <T> is
> illegal here. See also issue 147:
> http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#147
>

The thing I tried to point out in another post, but failed to do so properly
is that the "name specified after the nested-name-specifier" can be
interpreted to be both "S" and "S<T>": the identifier name "S" is nested
further down in the template-id, which itself is a name too. Both start at
the same location:

- So, if we look-up using the template-name into the class, we will lookup
the injected-class-name, and replace it by the constructor, which is what
the defect report intends, since it would make the explicit specialization
well-formed. This is how comeau/EDG behaves.

- But the alternative interpretation is that we lookup using the name "S<T>"
according to the rules in 14.2, which will first do a nested name lookup for
"S", and then qualify it with the template argument list, making that name
not refer to the injected-class-name anymore, but to the class directly, so
3.4.3.1/1a will not replace the result by a constructor reference. This
interpretation will make the explicit specialization in the DR ill-formed,
because the whole name "S<T>" is slurped, and made refer to the class. This
also makes "S<T>::S<T>" ill-formed of course, by not making it refer to the
constructor, of course. This seems to be how GCC behaves.

In my initial response, i thought the behavior is like the second
alternative, but after reading those DRs and reconsidered, i tend to agree
the right way is the first alternative. Do you also think this is an
ambiguity, or is the wording clear?

Nikolay Ivchenkov

unread,
Nov 12, 2009, 7:08:43 AM11/12/09
to
On 12 Nov, 11:43, "Johannes Schaub (litb)" <schaub-johan...@web.de>
wrote:
>

> The thing I tried to point out in another post, but failed to do so properly
> is that the "name specified after the nested-name-specifier" can be
> interpreted to be both "S" and "S<T>"

My point is that class-name denoted by template-id is not a name.

> Do you also think this is an
> ambiguity, or is the wording clear?

I think the standard shall differentiate "class name" and "class-name"
more strongly.

Nikolay Ivchenkov

unread,
Nov 12, 2009, 8:27:07 AM11/12/09
to
On 12 Nov, 03:59, "Johannes Schaub (litb)" <schaub-johan...@web.de>
wrote:
>

> I've rethought this, and wondered about this one:
>
> template<typename T>
> class F { template<typename U> F(U); };
>
> template<> template<>
> F<int>::F<int>(int);

AFAICS, this option is under discussion - see issue 581:
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#581

> 14.2 seems to indicate that
> name-lookup only cares about the template-name

This is right. See 3.4/1:

"The name lookup rules apply uniformly to all names (including typedef-
names (7.1.3), namespace-names (7.3) and class-names (9.1)) wherever
the grammar allows such names in the context discussed by a particular

rule. Name lookup associates the use of a name with a declaration
(3.1) of that name. Name lookup shall find an unambiguous declaration
for the name (see 10.2). Name lookup may associate more than one
declaration with a name if it finds the name to be a function name;
the declarations are said to form a set of overloaded functions
(13.1). Overload resolution (13.3) takes place after name lookup has
succeeded. The access rules (clause 11) are considered only once name
lookup and function overload resolution (if applicable) have
succeeded. Only after name lookup, function overload resolution (if
applicable) and access checking have succeeded are the attributes
introduced by the name�s declaration used further in expression
processing (clause 5)."

and 3/4:

"A name is a use of an identifier (2.10) that denotes an entity or
label (6.6.4, 6.1)."

So, the name lookup can be applied to identifiers only.

Johannes Schaub (litb)

unread,
Nov 12, 2009, 1:38:59 PM11/12/09
to
Nikolay Ivchenkov wrote:

> On 12 Nov, 03:59, "Johannes Schaub (litb)" <schaub-johan...@web.de>
> wrote:
>>
>> 14.2 seems to indicate that
>> name-lookup only cares about the template-name
>
> This is right. See 3.4/1:
>
> "The name lookup rules apply uniformly to all names (including typedef-
> names (7.1.3), namespace-names (7.3) and class-names (9.1)) wherever
> the grammar allows such names in the context discussed by a particular
> rule. Name lookup associates the use of a name with a declaration
> (3.1) of that name. Name lookup shall find an unambiguous declaration
> for the name (see 10.2). Name lookup may associate more than one
> declaration with a name if it finds the name to be a function name;
> the declarations are said to form a set of overloaded functions
> (13.1). Overload resolution (13.3) takes place after name lookup has
> succeeded. The access rules (clause 11) are considered only once name
> lookup and function overload resolution (if applicable) have
> succeeded. Only after name lookup, function overload resolution (if
> applicable) and access checking have succeeded are the attributes

> introduced by the name’s declaration used further in expression


> processing (clause 5)."
>
> and 3/4:
>
> "A name is a use of an identifier (2.10) that denotes an entity or
> label (6.6.4, 6.1)."
>
> So, the name lookup can be applied to identifiers only.
>

That's a defect in '03 though - a name actually includes also template-id's,
operator-function-id's and conversion-function-id's. See DR http://www.open-
std.org/jtc1/sc22/wg21/docs/cwg_defects.html#485 . The same issue with
"S<int>" still occurs in '0x where the name issue is fixed by that DR , and
0x has the following wording added as another case of "replace the class
with a constructor":

"if the name specified after the nested-name-specifier is the same as the
identifier or the simple-template-id’s template-name in the last component of
the nested-name-specifier"

This has the same problem with that "the name specified after ..." can apply
to both "S<int>" and "S". Not sure why it doesn't say:

"if the name specified after the nested-name-specifier is the same as the
identifier or the simple-template-id in the last component of the nested-
name-specifier."

Anyway, i'm wondering about a case where ".. is the same as the identifier"
catches something not already catched by "if the name specified after the
nested-name-specifier, when looked up in C, is the injected-class-name of C"
- haven't found a DR that added that wording.

Nikolay Ivchenkov

unread,
Nov 12, 2009, 1:35:36 PM11/12/09
to
On 12 Nov, 16:27, Nikolay Ivchenkov <ts...@mail.ru> wrote:
> So, the name lookup can be applied to identifiers only.

I know, there is inconsistence between 3/4 and 3/7, and the lookup
actually applies not only to identifiers. There may be several
approaches to solve the poor terminology of the standard. But in any
case I believe that template-id shall not be considered as name and
shall not be subject to the name lookup rules, because, in general,
template-id does not designate a member of class or namespace
introduced by a declaration (in contrast, template-name does). But


"Name lookup associates the use of a name with a declaration (3.1) of

that name.".

Defect report 485
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#485
affects a relationship between template-id and argument-dependent
lookup. I think, the problem with ADL can be carefully solved by
reformulating [basic.lookup.argdep] like:

When an unqualified name ** possibly followed by a template-argument-
list enclosed in <> ** is used as the postfix-expression in a function
call (5.2.2), other namespaces not considered during the usual
unqualified lookup (3.4.1) may be searched, and in those namespaces,
namespacescope friend function declarations (11.4) not otherwise
visible may be found.

Instead, the committee was decided to specify template-id as name (and
possibly unqualified name?). It's very weird, IMO. Actually a template-
name is subject for the name lookup, not a template-id.

0 new messages