Trouble understanding C++ dependent types, vs what

2020-07-27 04:06发布

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

1条回答
\"骚年 ilove
2楼-- · 2020-07-27 04:35

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!

查看更多
登录 后发表回答