3

The code below is adapted from the answer here: https://stackoverflow.com/a/17579889/352552

My purpose in asking this question is try to to understand better how C++ handles type resolution around dependent types, versus what's considered to be on the current instantiation, and therefore not needing a typename qualifier. I've been getting contradictory results from different compilers, so I've come here looking for a more canonical answer.

Consider this code

#include <iostream>

struct B {
  typedef int result_type;
};

template<typename T>
struct C {
};

template<>
struct C<float> {
  typedef float result_type;
}; 

template<typename T>
struct D : B, C<T> {
  std::string show() {
    //A) Default to current instantiation - ignore dependent type, even if one exists, or so I hope
    D::result_type r1;

    //B) What **exactly** does typename add, here?
    //typename D::result_type r1;

    return whichType(r1);
  }

  std::string whichType (int val){
    return "INT";
  }
  std::string whichType (float val){
    return "FLOAT";
  }    
};


int main() {  
  D<std::string> stringD;
  D<float> floatD;
  std::cout<<"String initialization "<<stringD.show()<<std::endl;
  std::cout<<"Float initialization "<<floatD.show()<<std::endl;
}

line A) in show(), if I understand correctly, tells the compiler to use the current instantiation, so I should get INT INT. On GCC, I do. So far, so good.

Line B, again if I understand correctly, should either tell the compiler to consider dependent types, which would make that line error out because of the ambiguity; or, if that means only consider dependent types, I should get INT FLOAT. On GCC I get INT INT there, too. Why?


Running this on Clang.

Line A doesn't compile at all.

error: no type named 'result_type' in 'D'; did you mean simply 'result_type'? D::result_type r1;

dropping the D:: does indeed yield INT INT.

Should it have compiled, or is Clang correct here?

Line B does indeed error on the ambiguity

error: member 'result_type' found in multiple base classes of different types typename D::result_type r1


Can anyone here say with authority which compiler (if any!) is canonically correct, and why?

Assuming Clang is correct, it might imply that

MyType::F

is invalid for referencing a type from the current instantiation if it exists on a base type; it's only valid if the type is defined on that class. Ie adding

typedef double dd;

to D

and then

D::dd d = 1.1;
std::cout<<d;

in show would work just fine, which is indeed the case.

Moreover,

typename D::sometype

seems to mean consider dependent types, but not exclusively, and so expect errors if such a type winds up defined in multiple places, either in the current instantiation, and or dependent on a template parameter.

But again, this all assumes Clang's behavior is correct according to spec, which I can't speak to.


Link to GCC repl I was using: https://wandbox.org/

Link to Clang repl I was using: https://repl.it/languages/cpp11

Adam Rackis
  • 82,527
  • 56
  • 270
  • 393
  • 1
    I thought this explained it well: https://en.cppreference.com/w/cpp/language/dependent_name – Eljay Jun 01 '19 at 23:39
  • Why do you hope that? – curiousguy Jun 02 '19 at 01:09
  • 1
    @Eljay "_the program is ill-formed, no diagnostic required. This is possible in the following situations: a type used in a non-dependent name is incomplete at the point of definition but complete at the point of instantiation_" That REALLY doesn't sound right! – curiousguy Jun 02 '19 at 01:41
  • 1
    @Eljay Excellent and horrible explanation on the fine details of template lookup - I want to barf. – curiousguy Jun 02 '19 at 02:08
  • 1
    @curiousguy • you have echoed my sentiments. (Then again, I have a love-hate relationship with C++ in general, and I may be suffering from the Stockholm Syndrome.) – Eljay Jun 02 '19 at 14:57
  • 1
    @Eljay To have proper, clean, regular semantics for template with simple and reliable name lookup, you would have to make templates into something else completely, with a formal contract on each template. Names wouldn't be looked up in many places and dependent bases wouldn't be a special case. – curiousguy Jun 02 '19 at 17:23
  • @curiousguy • Alas, that wouldn't be C++. Maybe it'd be something like templates in the D programming language. https://dlang.org/spec/template.html – Eljay Jun 02 '19 at 18:36
  • Please can you clearify in your question what "with authority" is supposed to mean? – Johannes Schaub - litb Jun 02 '19 at 20:13
  • 1
    The phrase "considers dependent types" doesn't make sense at all. Perhaps you meant "looks into dependent base classes". But that are different assertions. You could have said "typedef T result_type;" within `D` and then used `D::result_type` or plain `result_type` within `show` and you would get a dependent type (because it's `T` and `T` is a dependent type). – Johannes Schaub - litb Jun 02 '19 at 20:24
  • @JohannesSchaub-litb - I meant `considers dependent types` to mean allows dependent types (ie, types dependent on template type instantiations - you called them `type-dependent name` in your answer - sorry if I used the wrong terminology) to be considered by the compiler when resolving `D::result_type`. On Clang it does indeed do that, but only if I drop `D::`, in which case I get the expected error, as there's a clash / ambiguity. Without `typename` here, the dependent types are not considered, and so Clang resolves it to `B::result_type` but again, only if I drop the `D::` – Adam Rackis Jun 02 '19 at 23:30
  • @JohannesSchaub-litb has the C++ spec changed since you wrote the code from your answer? Or is Clang wrong to reject your original code as a compile error? – Adam Rackis Jun 02 '19 at 23:32
  • @JohannesSchaub-litb `Please can you clearify in your question what "with authority" is supposed to mean?` - basically just a non-speculative answer as to which compiler is right, for the various behaviors I'm seeing above, and why. – Adam Rackis Jun 02 '19 at 23:34
  • 2
    Your code as written is invalid, because you instantiate `D::show`. If you don't, and clang still rejects it, you can file a bug report at bugs.llvm.org . Looks like clang just hasn't implemented C++11 completely yet. This wouldn't be the first C++11 improvement that they haven't yet implemented. I've reported this other bug in the past, but they haven't yet implemented correct class member name lookup: https://bugs.llvm.org/show_bug.cgi?id=5916 – Johannes Schaub - litb Jun 03 '19 at 19:32

1 Answers1

3

Moreover,

typename D::sometype

seems to mean consider dependent types

Where did you get that idea? typename only means that what follows isn't a data member but a type name, so that the parsing of a template can be done. Do you know how primitive C++ compilers parsed template functions and classes in the old time? They did no meaningful parsing, they just ate all symbols doing only {/} balancing. Yes at some point you could include almost any garbage inside template definitions if they were never instantiated! It was simplistic and dirty but not that inane if you think about it, as the alternative (correct parsing) was not really practicable at the time.

In order to even meaningfully parse (without even resolving many names) inside a template some things need to be made explicit: the category (variable-or-function, type name, template name) of symbols that can't be resolved before instantiation, so simple stuff like X * Y;, X * (Y); and X(Y); is ambiguous and not parsable (declaration or expression). So typename is used to indicate that a symbol that can't be found at template definition time designate a type, so if X is typename T::U then all three previous syntagmes are declaration; without the typename and if T::U is dependent they would be parse as expression-statements and there is no second parsing when templates are instantiated so if U was actually a type it would be an error.

//A) Default to current instantiation - ignore dependent type, even if one exists, or so I hope
D::result_type r1;

According to https://en.cppreference.com/w/cpp/language/dependent_name lookup from the "current instantiation" considers only non dependent base classes at definition time and then:

If the lookup of a member of current instantiation gives a different result between the point of instantiation and the point of definition, the lookup is ambiguous.

So hopefully what you "hoped" for shouldn't happen!

curiousguy
  • 8,038
  • 2
  • 40
  • 58
  • `Where did you get that idea? typename only means that what follows isn't a data member but a type name, so that the parsing of a template can be done` — but what I describe is exactly how Clang behaves. Without `typename` I get the type on the current instantiation. With it, the dependent type is considered, and I get an error on the ambiguity. But then you go on to basically describe, in other terms, that yes, `typename` tells the compiler to consider types dependent on the actual template instantiation – Adam Rackis Jun 02 '19 at 15:31
  • @AdamRackis No, `typename` indicates that a dependent name that cannot be known before template instantiation names a type (and not a variable or function). The prefixing with `D::` makes the name dependent. If a name wasn't interpreted as a type name during template definition and ends up being one at template instantiation, it's an error. Also if lookup finds an ambiguity at template instantiation it's an error even if the name wasn't ambiguous at template definition time. **Dependent classes shouldn't be ignored for a dependent name lookup whether it's a type or a non-type.** – curiousguy Jun 02 '19 at 17:32
  • @curiousguy +1. I "hoped" that should have been clear from my answer (I also ephasized that the compiler is required to diagnose that ambiguity, in my answer). But if you want, you are welcome to clarify my answer in the other question of any confusion. – Johannes Schaub - litb Jun 02 '19 at 20:27
  • @curiousguy "The prefixing with D:: makes the name dependent". I would disagree, because the name neither refers to a dependent type (it has been resolved to `int`), nor is it a type-dependent or value-dependent expression. – Johannes Schaub - litb Jun 02 '19 at 20:38
  • 2
    However the spec has always been somewhat unclear about what a "dependent name" really is and I suppose the sentence "Inside a template, some constructs have semantics which may differ from one instantiation to another. Such a construct depends on the template parameters." can be interpreted to make *any* name which has a different meaning at instantiations be a "dependent name". – Johannes Schaub - litb Jun 02 '19 at 20:44
  • If that's the intention, I'm unsure whether it's actually useful to talk about "dependent name" at all though, because we will lack clear rules about the term. – Johannes Schaub - litb Jun 02 '19 at 20:54
  • Is the `F` in `F f` in `struct F; template void g() { F f; }` a dependent name?? Because in one instantiation, I could define `F` (make it a complete type), and in another instantiation I could leave it incomplete. So its semantics may differ from one instantiation to another, no? – Johannes Schaub - litb Jun 02 '19 at 20:54
  • @JohannesSchaub-litb I don't see how it matter that the same type is complete or incomplete (except here where is needs to be complete). This shouldn't be a problem: `struct F; template void g() { F *f; } ... g(); struct F {}; ...g();` – curiousguy Jun 02 '19 at 21:25
  • @curiousguy in this particular case it would make the name a dependent name, but this paragraph is supposed to apply: http://eel.is/c++draft/temp.res#8.5.1 . But it can only apply if the name is *non*-dependent. The wording at "the interpretation of such a construct in the hypothetical instantiation is different from the interpretation of the corresponding construct in any actual instantiation of the template." nearly exactly recites the meaning of "dependent constructs"! Therefore, I interpret the opening words of http://eel.is/c++draft/temp.res#temp.dep-1 as really just that. – Johannes Schaub - litb Jun 02 '19 at 21:28
  • @JohannesSchaub-litb The meaning of a name might be the same in all instanciations, if its meaning isn't known inside the template definition is must be a dependent name, IMO. In `g()` the meaning of identifier `F` is always a type whose fully qualified name is `::F`. So here `F` is non dependent. – curiousguy Jun 02 '19 at 21:33
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/194346/discussion-between-curiousguy-and-johannes-schaub-litb). – curiousguy Jun 02 '19 at 21:34
  • @curiousguy why would "the meaning" of a name only refer to the set of declarations in its lookup-result? IMO that would require a definition from the spec. It also sounds problematic: names that are looked up at instantiation might *always* yield to more/other declarations being found. Therefore all names must be dependent! And therefore, we need to lookup all names in the instantiation context... wait, aren't we running in circles now? So what if in the instantiation context of `g` we have `struct F; void F();` ? Then `F *f;` has a different lookup-result in that instantiation. – Johannes Schaub - litb Jun 02 '19 at 21:58
  • @JohannesSchaub-litb "_Therefore all names must be dependent_" That was the semantics of primitive C++ compilers. Stroustrup explained in D&E why it's a bad idea (it's very dirty, you can catch declarations you never wanted to catch). – curiousguy Jun 02 '19 at 22:01
  • @curiousguy I'm totally agreeing with you here. But if that's your interpretation, I still don't understand why you say that `D::result_type` is dependent. Because compilers are required to do an additional lookup according to http://eel.is/c++draft/temp.res#temp.dep.type-8 in the instantiation context? What, then, is the consequence of `D::result_type` being dependent? My position is that it doesn't matter at all. It kind of (remotely) is a "dependent construct" in terms of that chapter introduction, just like "F *f". But that statement doesn't have any consequence at all. – Johannes Schaub - litb Jun 02 '19 at 22:06
  • @JohannesSchaub-litb It may NOT be dependent then... that's subtle. I might have written BS. – curiousguy Jun 02 '19 at 22:17
  • @AdamRackis curiousguy wrote: "_The prefixing with D:: makes the name dependent_" can SOMETIMES make the name dependent... – curiousguy Jun 02 '19 at 22:19
  • @curiousguy thanks, that's what confused me. I'm back dreaming. – Johannes Schaub - litb Jun 02 '19 at 22:20