7

This question is a follow up of Moving a member function from base class to derived class breaks the program for no obvious reason (this is a prime example of why one shouldn't use using namespace std;)

where the answers suggested qualifying by this-> a dependent template name (which indeed is the way to go when referring to such dependent members). However, there seems to be an issue, so I'll list a minimal example that reproduces the problem.

Consider the code:

#include <iostream>
#include <bitset>

using namespace std;

template<class T>
struct B
{
    T bitset{};
};

template<class T>
struct D : B<T>
{
    bool foo()
    {
        return this->bitset < 32; 
    }
};

int main(){}

Live on Coliru

The perplexing thing is that even though this->bitset should refer to the member B<T>::bitset, the compiler is still confused and believes that we try to refer to std::bitset<std::size_t>. The error appears on both gcc6 and clang3.7. Any ideas why this happens? Qualifying it with B<T>::bitset works though.

Error (verbatim):

In member function 'bool D<T>::foo(T, std::__cxx11::string)': cpp/scratch/minimal.cpp:24:22: error: invalid use of 'class std::bitset<1ul>'

EDIT

This looks to me like a parsing/name lookup bug. If we replace < by any other comparison operator (thanks @Leon for the remark), e.g.

return this->bitset == 32; 

the program compiles. So I guess in this->bitset < 32 the parser believes that we are trying to instantiate a template (the < sign), and we forgot to close the >. But again have no idea if this is indeed a bug or that's how the language is suppose to work.

Community
  • 1
  • 1
vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • 2
    I think the compiler is just telling you subtly that you really should be using std::bitset ;) instead of a repurposed integer. – rubenvb Nov 07 '16 at 17:10
  • 1
    Probably because of the way `name-lookup` works ? `name-lookup` in a simple case works by checking the derived namespace and then the base namespace and then the global scope. Since, `Base` here is dependent, it cannot look into its scope and figure it out until it finds an instantiation. – Arunmu Nov 07 '16 at 17:11
  • @Arunmu: I expect name lookup to be deferred in the case of data members and member functions; this seems to be different. – Matthieu M. Nov 07 '16 at 17:14
  • @MatthieuM. I think Arunmu may be right. Even with an empty `main()` we still get the error. So the error appears before the secondary name look-up. I gotta go back to http://www.josuttis.com/tmplbook/ and see what the exact rules are :) – vsoftco Nov 07 '16 at 17:15
  • @MatthieuM. Well I may be wrong (I always get confused in such cases). But, `bitset` is not a dependent name and thus should be looked up at the time of template instantiation. It's pretty late here for me to open the standard and check :P – Arunmu Nov 07 '16 at 17:20
  • It is also worth noting that `return this->bitset == 32` compiles without a problem. – Leon Nov 07 '16 at 17:56
  • @Leon Ohhh that's interesting. Then I probably know what's going on: the parser believes we are trying to open an `<` to instantiate the template `bitset<>`. It compiles fine with any other comparison operator except `<`. But to me it looks like a combination between a parsing bug and name lookup. – vsoftco Nov 07 '16 at 17:59
  • Explicitly qualifying it `return this->B::bitset < 32:` does the trick. (Do not use `using namespace X` in a header.) – Pixelchemist Nov 07 '16 at 18:00
  • 1
    @Pixelchemist Yes, that I knew, you don't even need `this->`, and I don't use `using namespace` in general. The question is why non-qualified name doesn't work, as it looks like a very curious parsing issue. – vsoftco Nov 07 '16 at 18:01
  • I tried doing this in VS2015 and it compiles and builds fine. I'm thinking this could be a compiler issues that is strictly related to GCC or Clang and that either A, they don't know how to parse it properly to resolve the independent name lookup or B, they are more verbose or strict with the language than MSVS. I'm not sure about XCode or Intel's compiler either. I do just know that it does compile and build on Visual Studio 2015 but I have not tested it to see if it actually runs. – Francis Cugler Nov 13 '16 at 19:04

1 Answers1

7

tl;dr it looks like this is a deliberate decision, specifically to support the alternate syntax you already used.

An approximate walkthrough of the standardese below:

this-> B <
         ^
  • this could be either the start of a template id or a less-than, so let's check both!
    1. this->B does name something, but it's a template B<T>, so keep going
    2. B on it's own also names something, a class template B<T>
    3. wait, they're the same thing! That means we're using this->B<T> as a qualifier, and it isn't a less-than after all

In the other case,

this->bitset

proceeds identically until the third step, when it realises there are two different things called bitset (a template class member and a class template), and just gives up.


This is from a working draft I have lying around, so not necessarily the most recent, but:

3.4.5 Class member access [basic.lookup.classref ]

1 In a class member access expression (5.2.5), if the . or -> token is immediately followed by an identifier followed by a <, the identifier must be looked up to determine whether the < is the beginning of a template argument list (14.2) or a less-than operator. The identifier is first looked up in the class of the object expression. If the identifier is not found, it is then looked up in the context of the entire postfix-expression and shall name a class template. If the lookup in the class of the object expression finds a template, the name is also looked up in the context of the entire postfix-expression and

  • if the name is not found, the name found in the class of the object expression is used, otherwise
  • if the name is found in the context of the entire postfix-expression and does not name a class template, the name found in the class of the object expression is used, otherwise
  • if the name found is a class template, it shall refer to the same entity as the one found in the class of the object expression, otherwise the program is ill-formed.

So, in any expression like this->id < ..., it has to handle cases where id<... is the start of a template identifier (like this->B<T>::bitset).

It still checks the object first, but if this->id finds a template, further steps apply. And in your case, this->bitset is presumably considered a template as it still depends on T, so it finds the conflicting std::bitset and fails at the third bullet above.

Useless
  • 64,155
  • 6
  • 88
  • 132
  • 3
    Thanks, I gotta say this was one of the most obscure errors I've ever seen in a C++ code. – vsoftco Nov 07 '16 at 18:24
  • Agreed, it's sort of surprising. Presumably at some stage it becomes impossible to decide what an expression means without some kind of rule. – Useless Nov 07 '16 at 18:32
  • I guess that having an ambiguous grammar to start with, at some point you just need to bail out :/ – Matthieu M. Nov 07 '16 at 19:41
  • Yeah the original (linked) question is probably a good demonstration of why it's better to bail out early than to just keep eating tokens and hope it eventually makes sense ... – Useless Nov 07 '16 at 19:48
  • 1
    As the defect report says, the trouble begins with (in our example) `bitset` being a dependent name. I thought this was what the `template` disambiguator was for, e.g. `this->template bitset<` if you want to avoid treating the `<` as a less-than operator. – Oktalist Nov 08 '16 at 00:09