6

I have the following snippet.

template< typename T >
struct f
{
  template< typename V >
  struct a : f
  {};
};

int main ()
{
  f<int>::a<int>::a<double> x;
}

It compiles with no warnings on GCC 4.4.5 and also MSVC 2010, but not on GCC 4.5.2 -- on which I get the following errors:

test.cc: In function 'int main()':
test.cc:11:21: error: expected primary-expression before 'double'
test.cc:11:21: error: expected ';' before 'double'

So while I don't see anything non-standard about it, the question is obligatory -- is this legal in C++? Also, if it is, how do I file a bug report at GCC? (:

edit: A little background for the curious:

This is supposed to be a piece of template metaprogramming. f basically has the structure of a template metafunction class with apply substituted for a (of course the nested type of apply is omitted so we can concentrate on the structure itself).

Inheritance in this case is a standard device for binding metafunction return values. What this snippet is trying to achieve is a metafunction class that recursively yields itself when evaluated.

edit2: let me put the same snippet a bit differently:

template< typename T >
struct f
{
  template< typename V > struct a;
};

template< typename T >
template< typename V >
struct f<T>::a : f<T>
{};

int main ()
{
  f<int>::a<int>::a<double> x;
}

This produces the same error. I think it refutes the incomplete type argument.

xcvii
  • 450
  • 3
  • 17
  • 7
    I don't think this is valid. `f` is incomplete when `a` is declared so it cannot be used as a base class. – James McNellis May 01 '11 at 21:49
  • Is the template stuff a red herring? What happens if you just make it a bunch of normal classes? – Oliver Charlesworth May 01 '11 at 21:53
  • @OliCharlesworth I think what James said would definitely apply if it were normal classes. – xcvii May 01 '11 at 22:04
  • @xcvii: Then why would it be any different for class templates? – Oliver Charlesworth May 01 '11 at 22:05
  • @OliCharlesworth because of lazy template instantiation? Just guessing (: – xcvii May 01 '11 at 22:08
  • The rules might be different because `f` is complete when `a` is first instantiated. I don't know whether that would make the results well-defined or undefined or no different from when templates are not used. – James McNellis May 01 '11 at 22:21
  • The template class `a` doesn't contain another template class `a`. Therefore, `a::a` doesn't exist. gcc 4.6 complains with the same error message as given above, but it doesn't complain when I instantiate the templates using `f::a::a`. Interesting. – evnu May 02 '11 at 05:58
  • @xcvii In my opinion it is invalid to not specify template parameters with template class and you do just that when inheriting from f. f is a template so it should have template params specified. – There is nothing we can do May 02 '11 at 06:50
  • I want this code to generate an error, even if it isn't supposed to. :-) – Omnifarious May 02 '11 at 06:57
  • @There is nothing we can do: I am unsure about this, within the definition of `f` we typically elide the template parameters and suppose they are declared implicitly whenever we write a function using it, so it might be applicable to a base class. – Matthieu M. May 02 '11 at 07:06
  • @Matthieu according to Vandervoorde and Josuttis (unfortunately cannot locate appropriate chapter in their Templates - complete guide book) you should provide full template id in any doubious situations. And here we have ambiguity in a sense that is f going to be f or f? – There is nothing we can do May 02 '11 at 11:52
  • @There is nothing we can do: I don't think it's ambiguous here, since the type `f` is locally injected as `f`. Here is a little snippet on ideone using a class name without template parameters in a template method: http://ideone.com/kDhdU – Matthieu M. May 02 '11 at 13:06
  • @Matthieu I do understand what you're talking about, but how can you be sure that f is localy injected and not f? The way to do it is to get typedef of T from f and check what is the type in f after deriving from it in A. After that you will be sure one way or the other ;) – There is nothing we can do May 02 '11 at 13:20
  • @Matthieu looking few seconds at the example you've provided, apart from the fact that one shouldn't really use leading underscores, what I'm trying to say is for the same reason why you provided "full" template id in Create(**Demo** const& v) for the same reason one should while inheriting from class template also provide full template id. – There is nothing we can do May 02 '11 at 13:29
  • @There is nothing we can do: I agree it would be best to precise it (for the human reader) whenever an ambiguity may arise. I just say it's unnecessary as far as the compiler is concerned. Regarding the leading underscore: it's perfectly acceptable here, because we are not at global file scope but within a class. – Matthieu M. May 02 '11 at 13:31
  • @Matthieu I think the example from 9.2.3 Injected Class Names from Templates - The complete guide should be taken as a clue for this problem. The line from this example: X c; // ERROR: C without a template argument list //does not denote a template . And I know that here the story is bit different but none the less I'm convinced that full template id should be used. – There is nothing we can do May 02 '11 at 13:32
  • @Matthieu I think you'll find that leading underscore is reserved for library writers and the scope of the variable doesn't matter. But I know it was just quick example, none the less I personally believe that if in small thing one is prone to make mistakes in big thing he will make them too. ;) So what I'm trying to say (and this is nothing personal it is just my general opinion) it doesn't matter for what purpouse your examples are, they should always be written using good programming practices. Just so the future "less experienced" reader won't be "mislead". Would you agree on that? – There is nothing we can do May 02 '11 at 13:35
  • @There is nothing to do: I understand the point about `C`, effectively in a template parameter a type should be fully spelled out. However I am unsure whether it's required for a base class (the same way that `typename` is forbidden for a base class for example) and since it seems that Clang 2.9 accepts it it's probably fine since it explicitly aims for conformance. Regarding the leading underscore: no, it's fine. The exact rule is: `_[a-z]\w*` is only reserved at global scope while `_[A-Z]\w*` and `\w*__\w*` (2 underscores) are reserved whatever the scope. – Matthieu M. May 02 '11 at 14:20
  • @Matthieu and from where did you read about the underscore rules you've provided? Would you mind to provide link? And another thing, this time about templates, I am also unsure for I'm just a student IT not a C++ guru, but my opinion is based on the fact that it is just ambigous to a reader (me in this case) which instance of f will be A inheriting, f or f? And as Bjarne says about coding in clear style (somewhere in C++ - Principles and Practice): if you're unsure about something, make it clear for others and yourself. Waiting for the source about this underscores business. – There is nothing we can do May 02 '11 at 15:36
  • @Matthieu I did some looking around about the whole underscore business and look what I've found http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier, amongst others you'll find this rule: "All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces." – There is nothing we can do May 02 '11 at 15:48
  • @There is nothing we can do: wrt identifiers, as I said, `_[a-z]\w*` is reserved at file scope (global namespace) but fine otherwise. Wrt inheritance, I agree that `f` is just clearer for the reader, but compilers should implement the specifications nonetheless :) – Matthieu M. May 02 '11 at 15:54
  • @Matthieu and what do you understand by tag name space? – There is nothing we can do May 02 '11 at 16:00
  • @There is nothing we can do: not much. I tried looking it up and found: http://publib.boulder.ibm.com/infocenter/iadthelp/v7r1/index.jsp?topic=/com.ibm.etools.iseries.langref.doc/as400clr13.htm but it didn't helped me. It's definitely different than `namespace` since it also applies to `C`. I had based my opinion on the previous section of the post (the small list at the top). – Matthieu M. May 02 '11 at 16:25
  • @Matthieu what I understand by tag namespace is a class (which is a namespace by the way) or struct. But Matthieu, it really doesn't matter if one or other standard forbids, or not. I believe that starting anything in your code with underscore either it will be signle or double is just a bad programming practice, it is my personal opinion but would you agree on that with me? – There is nothing we can do May 02 '11 at 17:04
  • @There is nothing we can do: I will certainly agree that the simplest way (given the round about rule) is not to use leading underscores at all. Unfortunately the naming convention I have at work stipulates that class/struct attributes should begin with a leading underscore (followed by a lower case letter) so I got the habit :/ I am not sure that it actually applies to classes, enums most certainly since they inject the name in the surrounding namespace... Anyway, since we (at work) don't use any class at global namespace level, I guess we don't violate this rule. – Matthieu M. May 02 '11 at 18:21
  • @There is nothing we can do: I have never understood why no compiler seem to have a warning about this. It seems like a trivial check, really, and would definitely help out. – Matthieu M. May 02 '11 at 18:23
  • @Matthieu: It might be useful if done right. One problem is that reserved names are used by the Standard Library implementation, so once the source is preprocessed, it isn't necessarily obvious which names came from where. That could certainly be worked around, but it's one potential hinderance to making that kind of warning useful. – James McNellis May 02 '11 at 18:34
  • @Matthieu I couldn't agree more. GCC seems to have some switch which is suppose to warn you if you're coding not according to "good programming practice" but I'm not sure if it catches this kind of "violation". What I follow in my daily routine is to use as simple rules/code as possible (but not simpler ;) and I think it seems to work for me. – There is nothing we can do May 02 '11 at 18:35
  • @James but couldn't the compiler writers do something that would detect if such code is in user code and issue a warning? I think it is reasonable approach. – There is nothing we can do May 02 '11 at 18:37
  • @James, @There: GCC page (http://gcc.gnu.org/onlinedocs/cpp/Pragmas.html) lists `#pragma GCC system_header`. As far as I know, it is used to suppress any warning. Clang also suppresses all warnings emitted by system headers (I am not sure how it recognizes them when parsing microsoft ones). The preprocessed files still retain information about the file the code comes from, and indeed gcc, CLang, Comeau and MSVC all acurately track the source location of errors and warnings, so it seems definitely feasible. Of course, you still need to allow the use of those symbols defined in system headers :) – Matthieu M. May 02 '11 at 19:27

4 Answers4

4

There were a couple of good notes in existing answers. First, f's type is incomplete at the time the nested class template is defined, but f is a dependent type. Now, if you instantiate the nesting template (f), it will instantiate a declaration of the nested template (the member). Note that the declaration of the member does not include the base clause list, so it doesn't need complete base classes. Once the nesting template has been instantiated implicitly, f is complete and when it comes to instantiating the definition of the member, there should be no issue anymore. So I don't think that comeau is correct complaining here.

The other bug is that, in fact, f<int>::a<int>::a is naming the constructor of a<int>, and requires it to be a constructor template (with <int> being the template arguments). The base of this was DR #147.

The translation to the constructor is not done when the qualifier name isn't the class of the injected class name. For example, if it is a derived class, your code becomes valid (as some answers figured out).

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • nice, thanks! although i'm not sure if i completely grasp it yet: so basically every appearance of `A::A` (where `A` is a class) denotes the constructor? even if i try to access a member of the "second" `A`? – xcvii May 03 '11 at 07:44
  • @xcvii, no. If you try to access a member and use `::`, then not, because lookup for a name prior to `::` only considers class and namespace names (and in C++0x, enumerations), ignoring all other names. I.e `struct Foo { typedef int bar; }; Foo::Foo::bar x;` is valid. – Johannes Schaub - litb May 03 '11 at 17:11
  • @Johannes ok, so suppose `a` in the original snippet has a nested typedef `t`. if i understand right, it makes `f::a::a::t x;` a valid declaration. is that correct? – xcvii May 03 '11 at 17:29
  • 1
    @xcvii, yes, that should be alright. When looking up `a`, it should only consider classes, enumerations, class templates and (for C++0x) alias templates. It should ignore function templates and function names (in other words, it should not translate the `a` to a constructor name). However this was clarified only in C++0x which added ".. and templates whose specializations are types." to the list that are only considered. GCC appears to not apply this restriction of lookup to `a`, since it still does the constructor name translation. – Johannes Schaub - litb May 03 '11 at 17:40
  • Just noticed this was your 2000th answer (with most of my questions in those). Nice one! – Tom May 05 '11 at 19:38
2

Nice question. This seems to be an issue with gcc for template recursive declaration. Because, had there been solid classes then it gives error and ideally it should be declared as:

struct Out {
  struct In;
};
struct Out::In : Out {};
iammilind
  • 68,093
  • 33
  • 169
  • 336
  • also, with this example, the following seems to work: `int main() { typename Out::In::In x; }` although i wasn't able to transfer this pattern to the original code – xcvii May 02 '11 at 18:28
  • @xcvii, that is a known bug in GCC. `typename Out::In::In` should be invalid, because `typename` should *not* ignore non-type names (however, GCC doesn't follow that rule). So the injected-class-name2constructor translation should still be done. But you can say `struct Out::In::In x;` and it should be fine. – Johannes Schaub - litb May 02 '11 at 19:49
  • @Johannes, "`typename` should _not_ ignore non-type names" -- what do you mean by this? what is the non-type name it erroneously ignores here? – xcvii May 03 '11 at 07:46
  • @xcvii the translation from `In::In` (the injected class name) to the constructors of `In` is only done if the lookup context for `In::In` doesn't ignore non-type names. For example `struct In::In` is fine. But `typename In::In` is not. GCC makes `typename` ignore non-type names: `struct A { int B; struct B { }; }; `typename A::B a;` GCC compiles this example, but it should not do so - the `A::B` should fail, and refer to the data member. – Johannes Schaub - litb May 03 '11 at 17:10
1

it looks like GCC 4.5 believes you have specified the constructor.

one possible workaround:

template<typename T>
struct f {

    template<typename V>
    struct a : f {

        template<typename Z>
        struct q {
            typedef a<Z> Q;
        };
    };
};

int main() {
    f<int>::a<int>::q<double>::Q x;
    return 0;
}
justin
  • 104,054
  • 14
  • 179
  • 226
0

Using Comeau online, it suggests that f is an incomplete type at the point you use it as a base class, and as such is unsuitable.

"ComeauTest.c", line 5: error: incomplete type is not allowed
    struct a : f

I don't have a clang instance at hand unfortunately.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722