8

I was playing around with templates today to see if I could get the compiler to deduce the type of an outer class from one of its inner classes. I didn't find my solution (which I suspect is impossible), but while trying to fix an error I ran into very strange behavior that I reduced to the following snippet.

struct A
{
    struct B{};

    template <typename T>
    struct EverythingIsFine
    {
        using Outer = T;
        using Inner = typename T::B::B::B::B::B::B;
    };

    using ItWillBeOkay = EverythingIsFine<B>; // Probably not ok
    using InnerProblem = ItWillBeOkay::Inner; // Still not ok
    using OuterProblem = decltype(B().ItWillBeOkay::Outer::B::B::B
                                     ::B::B::B::~B()); // Not even CLOSE to ok
};

It surprisingly compiles with no warnings and no errors with both Clang and GCC.
The versions of my compilers are gcc version 5.3.1 20160121 (Debian 5.3.1-7) and Debian clang version 3.6.2-3 (tags/RELEASE_362/final) (based on LLVM 3.6.2) and the flag used to compile are -std=c++11 -Wall -Wextra.

I observed that it also compiles fine on Ideone with the C++14 setting.


I then used this simple test to get the exact types of InnerProblem and OuterProblem:

template <class T> void Type();
int main()
{
    Type<A::InnerProblem>();
    Type<A::OuterProblem>();
}

And both compilers report the same types when compiling the test:

In function main:
main.cpp:20: undefined reference to void Type<A::B>()
main.cpp:21: undefined reference to void Type<void>()

That is to say, the type of InnerProblem is A::B and the type of OuterProblem is void.


Is this somehow permitted by the standard or is it a bug in both compilers?
And since I seem to be as confused as my compiler, what is actually happening with this code?

EDIT: As a simplified followup, because I don't understand why two compilers can not give the same result, the following code compiles with Clang, but not with GCC.

struct A
{
    struct B{};

    template <typename T>
    struct EverythingIsFine
    {
        using Inner = typename T::B::B::B;
    };

    using Problem = EverythingIsFine<B>::Inner::B::B::B; // Not ok
};

GCC now outputs the following error:

main.cpp:11:26: error: 'A::B::B' names the constructor, not the type using InnerProblem = EverythingIsFine::Inner::B::B::B; // Not ok

tux3
  • 7,171
  • 6
  • 39
  • 51
  • 2
    This code compiles on ICC and MSVS as well – NathanOliver Feb 02 '16 at 19:42
  • New question -> new question please. Include a link if the context helps. – Baum mit Augen Feb 02 '16 at 19:56
  • @BaummitAugen I think that my new question would be a dup too, so I'll abstain this time, but you're right. – tux3 Feb 02 '16 at 19:59
  • 1
    This is not a duplicate. There's a difference between the question "why is this code valid", to which the answer is "injected class name", and the question "why is the class name injected". – Pete Becker Feb 02 '16 at 19:59
  • @PeteBecker The top answer there includes pretty much the same quote you provide, all the necessary information is there. No need to duplicate it here. – Baum mit Augen Feb 02 '16 at 20:01

1 Answers1

6

It's valid.

The class-name is also inserted into the scope of the class itself; this is known as the injected-class-name." (9/2).

So B::B names the class B, as does B::B::B, and so on.

EDIT:

So typename B::B names the class B, as does typename B::B::B, and so on.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • That makes sense, but if I replace `InnerProblem = ItWillBeOkay::Inner` by `InnerProblem = ItWillBeOkay::Inner::B::B::B;` now GCC fails to compile and Clang still compiles fine, is this well-defined behavior? GCC says "'A::B::B' names the constructor, not the type". – tux3 Feb 02 '16 at 19:48
  • @tux3 - I don't know. I didn't (and won't) follow all the nested typedefs in your question. – Pete Becker Feb 02 '16 at 19:49
  • @tux3 I haven't looked at your crazy nested names either, but judging from the error message, that might be a clang bug, see http://stackoverflow.com/q/32006122/241631 – Praetorian Feb 02 '16 at 19:54
  • I tried to make a simplified example to cut on the crazy nested namespaces, is this better? – tux3 Feb 02 '16 at 19:55
  • @Praetorian You're right, it looks like a dupe of http://stackoverflow.com/questions/32006122/injected-class-name-compiler-discrepancy now that I look at it. – tux3 Feb 02 '16 at 19:56
  • 3
    `B::B` names a constructor here. There's only few contexts in which it doesn't. I.e. no, it's not valid, but Clang is bugged. – Columbo Feb 02 '16 at 20:01
  • 1
    So my understanding after some reading is that this is [a bug in Clang](https://llvm.org/bugs/show_bug.cgi?id=13403) that GCC is correct in reporting, so I can't accept this answer. – tux3 Feb 02 '16 at 20:10
  • 1
    Your update is just as incorrect. typename-specifiers are *not* a context in which function names are ignored. – Columbo Feb 02 '16 at 21:47