• Why doesn't this build?

    From Vir Campestris@vir.campestris@invalid.invalid to comp.lang.c++ on Tue Dec 2 20:41:05 2025
    From Newsgroup: comp.lang.c++

    It's obviously extracted from a larger program, and I've done a bit of
    messing about to pin it down - and still can't see WTF is going on.

    Classes A and B both derive from std::pair. One of the classes to be
    stored in the pair is the templated class b, and a template parameter is supplied.

    In class A the template parameter is a fixed enum value.

    In class B it is a copy of the template parameter to class B, which is
    from the same enum.

    Yet A compiles while B doesn't. B doesn't think it has a pair as a base.
    Help!

    Andy

    ///////////////////////////////////////////////////
    #include <utility>

    enum e {E};

    template <e f> class b {};

    template <e f> class A : std::pair<int, b<E> >
    {
    A() : pair() {}
    };

    template <e f> class B : std::pair<int, b<f> >
    {
    B() : pair() {}
    };
    --
    I've spent far too long away playing with the project this comes from.
    Perhaps I should give it up. But it's decades since I wrote anything
    just for fun!
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c++ on Tue Dec 2 13:06:46 2025
    From Newsgroup: comp.lang.c++

    On 12/2/2025 12:41 PM, Vir Campestris wrote:
    enum e {E};

    template <e f> class b {};

    template <e f> class A : std::pair<int, b<E> >
    {
    -a-a-a-a-a-a-a A() : pair() {}
    };

    template <e f> class B : std::pair<int, b<f> >
    {
    -a-a-a-a-a-a-a B() : pair() {}
    };

    Time to get more explicit? Seems to work wrt:
    _________________________
    #include <utility>
    #include <iostream>

    enum e {E};

    template <e f> class b {};

    template <e f> class A : std::pair<int, b<E> >
    {
    A() : std::pair<int, b<E> >() {}
    };

    template <e f> class B : std::pair<int, b<f> >
    {
    B() : std::pair<int, b<f> >() {}
    };


    int main()
    {
    std::cout << "Hello..." << std::endl;

    return 0;
    }
    _________________________

    Does that help?
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Vir Campestris@vir.campestris@invalid.invalid to comp.lang.c++ on Tue Dec 2 21:15:06 2025
    From Newsgroup: comp.lang.c++

    On 02/12/2025 21:06, Chris M. Thomasson wrote:

    Time to get more explicit? Seems to work wrt:
    _________________________
    #include <utility>
    #include <iostream>

    enum e {E};

    template <e f> class b {};

    template <e f> class A : std::pair<int, b<E> >
    {
    -a-a-a-a-a-a-a A() : std::pair<int, b<E> >() {}
    };

    template <e f> class B : std::pair<int, b<f> >
    {
    -a-a-a-a-a-a-a B() : std::pair<int, b<f> >() {}
    };


    int main()
    {
    -a-a-a std::cout << "Hello..." << std::endl;

    -a-a-a return 0;
    }
    _________________________

    Does that help?

    Yes, It gets me out of my hole. But... Why do I need to be explicit in
    class B, but not in class A? The only difference is that the second pair parameter is variable not fixed.

    Thanks
    Andy
    --
    Do not listen to rumour, but, if you do, do not believe it.
    Ghandi.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c++ on Tue Dec 2 13:43:25 2025
    From Newsgroup: comp.lang.c++

    On 12/2/2025 1:15 PM, Vir Campestris wrote:
    On 02/12/2025 21:06, Chris M. Thomasson wrote:

    Time to get more explicit? Seems to work wrt:
    _________________________
    [...]
    _________________________

    Does that help?

    Yes, It gets me out of my hole. But... Why do I need to be explicit in
    class B, but not in class A? The only difference is that the second pair parameter is variable not fixed.

    Not exactly sure. Humm. I noticed that wrt the following:
    _____________________
    #include <utility>
    #include <iostream>

    enum e {E};

    template <e f> class b {};

    template <typename f> class A : std::pair<int, b<E> >
    {
    A() : std::pair<int, b<E> >() {}
    };

    template <e f> class B : std::pair<int, b<f> >
    {
    B() : std::pair<int, b<f> >() {}
    };


    int main()
    {
    std::cout << "Hello..." << std::endl;

    return 0;
    }
    _____________________

    works (aka compiles), but if I put a typename for e in class B wrt <e
    , the compiler cakes its pants.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andrey Tarasevich@noone@noone.net to comp.lang.c++ on Wed Dec 3 04:10:52 2025
    From Newsgroup: comp.lang.c++

    On Tue 12/2/2025 12:41 PM, Vir Campestris wrote:

    ///////////////////////////////////////////////////
    #include <utility>

    enum e {E};

    template <e f> class b {};

    template <e f> class A : std::pair<int, b<E> >
    {
    -a-a-a-a-a-a-a A() : pair() {}
    };

    template <e f> class B : std::pair<int, b<f> >
    {
    -a-a-a-a-a-a-a B() : pair() {}
    };


    It is the same issue as in this example

    template <typename T> struct B { int x; };

    template <typename T> struct D : B<T> {
    D() {
    x = 5; // error: 'x' was not declared in this scope
    }
    };

    When compiling the subclass `D`, unqualified name lookup is not
    performed in base classes that depend on template parameters of `D`. For
    which reason unqualified name `x` (supposedly inherited from base
    `B<T>`) is not visible from members of `D`.

    In your case the same thing happens.

    The unqualified name `pair` you refer to in your code is an _injected_
    name inside base class `std::pair<>`, i.e. it is actually a member of `std::pair`. In order to find that `pair` the compiler has to perform unqualified lookup inside the base class `std::pair<>`.

    But in the second case the unqualified lookup inside is not performed
    for reasons I described above. Which is why unqualified name `pair` is
    not found.

    If you use a qualified name, the problem will go away, albeit in this
    case you will also have to reiterate the template arguments

    template <e f> class B : std::pair<int, b<f>>
    {
    B() : std::pair<int, b<f>>() {}
    };
    --
    Best regards,
    Andrey
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c++ on Wed Dec 3 16:06:38 2025
    From Newsgroup: comp.lang.c++

    On 12/3/2025 3:05 AM, Vir Campestris wrote:
    On 02/12/2025 21:43, Chris M. Thomasson wrote:
    Not exactly sure. Humm. I noticed that wrt the following:
    [...]
    It occurred to me later - what compiler are you using? I've tried G++
    and clang++ on Unix. I don't have MS compiler available.

    Oh, I used on online GCC compiler for it. Have not tried it on MSVC yet.


    And I just realised I don't need that pair, just the templated base
    class b to show the problem.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andrey Tarasevich@noone@noone.net to comp.lang.c++ on Thu Dec 4 06:59:21 2025
    From Newsgroup: comp.lang.c++

    On Wed 12/3/2025 4:10 AM, Andrey Tarasevich wrote:

    It is the same issue as in this example

    -a template <typename T> struct B { int x; };

    -a template <typename T> struct D : B<T> {
    -a-a-a D() {
    -a-a-a-a-a x = 5; // error: 'x' was not declared in this scope
    -a-a-a }
    -a };

    When compiling the subclass `D`, unqualified name lookup is not
    performed in base classes that depend on template parameters of `D`. For which reason unqualified name `x` (supposedly inherited from base
    `B<T>`) is not visible from members of `D`.

    In your case the same thing happens.

    The unqualified name `pair` you refer to in your code is an _injected_
    name inside base class `std::pair<>`, i.e. it is actually a member of `std::pair`. In order to find that `pair` the compiler has to perform unqualified lookup inside the base class `std::pair<>`.

    But in the second case the unqualified lookup inside is not performed
    for reasons I described above. Which is why unqualified name `pair` is
    not found.

    If you use a qualified name, the problem will go away, albeit in this
    case you will also have to reiterate the template arguments

    -a template <e f> class B : std::pair<int, b<f>>
    -a {
    -a-a-a B() : std::pair<int, b<f>>() {}
    -a };


    ... when we deal with the `x` problem as in my small example, we can
    work around this issue either by using a qualified name

    D() {
    B::x = 5; // OK
    }

    or by referring to `x` through an explicit `this`

    D() {
    this->x = 5; // OK
    }

    In your case the workaround I provided above goes a different way: it
    refers to base class name directly (instead of referring to the injected name).

    But if you want, you can insist on referring to the injected name.
    Obviously, the `this->` version is not applicable here, but the
    qualified name approach will work

    template <e f> class B : std::pair<int, b<f> >
    {
    B() : std::pair<int, b<f>>::pair() {}
    };

    This is even longer, but in this version you are basically forcefully restoring your original intent.
    --
    Best regards,
    Andrey
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Vir Campestris@vir.campestris@invalid.invalid to comp.lang.c++ on Thu Dec 4 16:55:47 2025
    From Newsgroup: comp.lang.c++

    On 03/12/2025 12:10, Andrey Tarasevich wrote:
    <snip>
    If you use a qualified name, the problem will go away, albeit in this
    case you will also have to reiterate the template arguments

    -a template <e f> class B : std::pair<int, b<f>>
    -a {
    -a-a-a B() : std::pair<int, b<f>>() {}
    -a };

    "Problem" you say. Is this a compiler bug in both Gnu and Clang
    compilers, or is it supposed to be like it?

    Andy
    --
    Do not listen to rumour, but, if you do, do not believe it.
    Ghandi.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andrey Tarasevich@noone@noone.net to comp.lang.c++ on Thu Dec 4 17:57:28 2025
    From Newsgroup: comp.lang.c++

    On Thu 12/4/2025 8:55 AM, Vir Campestris wrote:
    On 03/12/2025 12:10, Andrey Tarasevich wrote:
    <snip>
    If you use a qualified name, the problem will go away, albeit in this
    case you will also have to reiterate the template arguments

    -a-a template <e f> class B : std::pair<int, b<f>>
    -a-a {
    -a-a-a-a B() : std::pair<int, b<f>>() {}
    -a-a };

    "Problem" you say. Is this a compiler bug in both Gnu and Clang
    compilers, or is it supposed to be like it?


    No, it is not a bug. It is supposed to be like it. The rule that
    excludes depended base classes from unqualified name lookup in C++17 is

    17.6.2 Dependent names [temp.dep]
    3 In the definition of a class or class template, the scope of a
    dependent base class (17.6.2.1) is not examined during unqualified name
    lookup either at the point of definition of the class template or member
    or during an instantiation of the class template or member.

    (In later versions of the standard it's been relocated somewhere and
    reworded in some way apparently. I can't even find it.)

    This unqualified lookup peculiarity for template base classes is also described in quite a few FAQs. E.g.

    https://isocpp.org/wiki/faq/templates#nondependent-name-lookup-types

    In your case it just happens to involve so called "injected class name",
    which is another fairly obscure C++ topic

    https://en.cppreference.com/w/cpp/language/injected-class-name.htm
    --
    Best regards,
    Andrey
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andrey Tarasevich@noone@noone.net to comp.lang.c++ on Thu Dec 4 22:00:42 2025
    From Newsgroup: comp.lang.c++

    On Thu 12/4/2025 5:57 PM, Andrey Tarasevich wrote:

    In your case it just happens to involve so called "injected class name", which is another fairly obscure C++ topic

    -a https://en.cppreference.com/w/cpp/language/injected-class-name.htm


    Lost a trailing 'l' in the link. Should be

    https://en.cppreference.com/w/cpp/language/injected-class-name.html
    --
    Best regards,
    Andrey
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Vir Campestris@vir.campestris@invalid.invalid to comp.lang.c++ on Sun Dec 7 21:18:31 2025
    From Newsgroup: comp.lang.c++

    On 05/12/2025 01:57, Andrey Tarasevich wrote:

    No, it is not a bug. It is supposed to be like it. The rule that
    excludes depended base classes from unqualified name lookup in C++17 is

    -a 17.6.2 Dependent names [temp.dep]
    -a 3 In the definition of a class or class template, the scope of a dependent base class (17.6.2.1) is not examined during unqualified name lookup either at the point of definition of the class template or member
    or during an instantiation of the class template or member.

    (In later versions of the standard it's been relocated somewhere and reworded in some way apparently. I can't even find it.)

    This unqualified lookup peculiarity for template base classes is also described in quite a few FAQs. E.g.

    https://isocpp.org/wiki/faq/templates#nondependent-name-lookup-types

    In your case it just happens to involve so called "injected class name", which is another fairly obscure C++ topic

    https://en.cppreference.com/w/cpp/language/injected-class-name.html

    (I fixed the last link name in that quote as you advised)

    The isocpp.org website just says

    "An Error Was Encountered
    Site Error: Unable to Load Site Preferences; No Preferences Found"

    I tried both Chrome and Firefox.

    I've read the other one, and the links it follows through, and I still
    don't understand.

    I found that I could drop pair from my examples, and I have this:

    enum e {Z};

    template <e f> class b {};

    template <e f> class C : b<Z>
    {
    // dependent name lookup finds b OK here
    // and knows it means the base class b<Z>
    C() : b() {}
    };

    template <e f> class D : b<f>
    {
    // In this case b<f> is not found, even
    // though it is the only base class
    D() : b() {}
    };

    It's not like the cases where a class has two bases with different
    template parameters. There is a single base class and no possible ambiguity.

    Thanks
    Andy
    --
    Do not listen to rumour, but, if you do, do not believe it.
    Ghandi.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andrey Tarasevich@noone@noone.net to comp.lang.c++ on Sun Dec 7 14:09:36 2025
    From Newsgroup: comp.lang.c++

    On Sun 12/7/2025 1:18 PM, Vir Campestris wrote:

    The isocpp.org website just says

    "An Error Was Encountered
    Site Error: Unable to Load Site Preferences; No Preferences Found"

    I tried both Chrome and Firefox.

    It appears that the whole site is currently down. It is a "come back
    later" kind of situation.

    I found that I could drop pair from my examples, and I have this:

    enum e {Z};

    template <e f> class b {};

    template <e f> class C : b<Z>
    {
    -a-a-a-a-a-a-a // dependent name lookup finds b OK here
    -a-a-a-a-a-a-a // and knows it means the base class b<Z>
    -a-a-a-a-a-a-a C() : b() {}
    };

    template <e f> class D : b<f>
    {
    -a-a-a-a-a-a-a // In this case b<f> is not found, even
    -a-a-a-a-a-a-a // though it is the only base class
    -a-a-a-a-a-a-a D() : b() {}
    };

    It's not like the cases where a class has two bases with different
    template parameters. There is a single base class and no possible
    ambiguity.

    That is exactly the same problem.

    Again, when you write `b` is the constructor initializer lists of
    classes `C` and `D`, you are actually referring to the
    injected-class-name `b`, which is invisibly present inside the `b<>`
    template class.

    Think of it this way: every time you define

    template <e f> class b {
    ...

    the compiler quietly inserts an implicit/invisible typename declaration
    as the very first line of your class template

    template <e f> class b {
    using b = /* the current specialization of `b<>` */;
    ...

    The existence of that nested `b` is the reason you can refer to the
    current specialization of template class `b<>` as a mere `b` (i.e.
    without explicitly specifying its template arguments). Jut like you
    previously referred to `std::pair<whatever>` as a mere `pair`.

    This `b`, implicitly introduced by the compiler, is the so called injected-class-name. It exists in all classes, template or non-template.

    And when you mention `b` in the constructor initializer lists of `C` and
    `D`, the unqualified name lookup looks for `b`. There's nothing else it
    can find besides the injected-class-name inside the `b<>` template. That injected-class-name `b` is the only chance for unqualified name lookup
    to succeed. And if for some reason that lookup fails, your code will
    fail to compile.

    For your `C` the lookup succeeds, since the base class `b<Z>` is not a dependent type. It is included into the lookup, so the compiler
    successfully finds `b<Z>::b`.

    But for your `D` the base class `b<f>` is a dependent type. It is not
    included into the lookup (for reasons I described previously). So,
    `b<f>::b` is not found. Lookup for `b` fails.
    --
    Best regards,
    Andrey
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Vir Campestris@vir.campestris@invalid.invalid to comp.lang.c++ on Mon Dec 8 17:33:42 2025
    From Newsgroup: comp.lang.c++

    On 07/12/2025 22:09, Andrey Tarasevich wrote:
    That is exactly the same problem.

    Again, when you write `b` is the constructor initializer lists of
    classes `C` and `D`, you are actually referring to the injected-class-
    name `b`, which is invisibly present inside the `b<>` template class.

    Think of it this way: every time you define

    -a template <e f> class b {
    -a-a-a ...

    the compiler quietly inserts an implicit/invisible typename declaration
    as the very first line of your class template

    -a template <e f> class b {
    -a-a-a using b = /* the current specialization of `b<>` */;
    -a-a-a ...

    The existence of that nested `b` is the reason you can refer to the
    current specialization of template class `b<>` as a mere `b` (i.e.
    without explicitly specifying its template arguments). Jut like you previously referred to `std::pair<whatever>` as a mere `pair`.

    This `b`, implicitly introduced by the compiler, is the so called injected-class-name. It exists in all classes, template or non-template.

    And when you mention `b` in the constructor initializer lists of `C` and `D`, the unqualified name lookup looks for `b`. There's nothing else it
    can find besides the injected-class-name inside the `b<>` template. That injected-class-name `b` is the only chance for unqualified name lookup
    to succeed. And if for some reason that lookup fails, your code will
    fail to compile.

    For your `C` the lookup succeeds, since the base class `b<Z>` is not a dependent type. It is included into the lookup, so the compiler
    successfully finds `b<Z>::b`.

    But for your `D` the base class `b<f>` is a dependent type. It is not included into the lookup (for reasons I described previously). So,
    `b<f>::b` is not found. Lookup for `b` fails.

    OK, I think I have it. I don't like it, but I understand.

    https://en.cppreference.com/w/cpp/language/dependent_name.html

    includes inter alia

    "a type used in a non-dependent name is incomplete at the point of
    definition but complete at the point of instantiation"

    as one of the reasons why it won't work.

    Many thanks
    Andy
    --
    Do not listen to rumour, but, if you do, do not believe it.
    Ghandi.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andrey Tarasevich@noone@noone.net to comp.lang.c++ on Mon Dec 8 20:17:20 2025
    From Newsgroup: comp.lang.c++

    On Mon 12/8/2025 9:33 AM, Vir Campestris wrote:

    OK, I think I have it. I don't like it, but I understand.

    https://en.cppreference.com/w/cpp/language/dependent_name.html

    includes inter alia

    "a type used in a non-dependent name is incomplete at the point of definition but complete at the point of instantiation"

    as one of the reasons why it won't work.


    I'm not sure what kind of "reasons" you are referring to.

    The immediate reason (i.e. "because the standard says so") is the rule I
    have already mentioned, linked and illustrated previously: dependent
    base classes are not inspected when performing into unqualified name
    lookup. (I see that isocpp.org is back online.)

    As for rationale-kind of reason (i.e. "why does standard say so?"), it
    is quite different. It is related to the fact in many template contexts
    the compiler need to know whether a given dependent name is a type name,
    a template name or a function/variable name. This is why we sometime
    have to explicitly spell-out such keywords as `typename` or `template`
    in template contexts to disambiguate the nature of some dependent names.

    A system for this kind of disambiguation exists in C++ for _qualified_
    names, but there's no such system for _unqualified_ names. Instead of expanding this system to cover unqualified names, the language simply
    decided to take the easy way out: postulate that names inherited from dependent base classes are "invisible" to unqualified name lookup. If
    you want to access such names, refer to them through their _qualified_
    names, thus engaging _qualified_ lookup and all the rules it brings with it.
    --
    Best regards,
    Andrey
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c++ on Tue Dec 9 08:56:46 2025
    From Newsgroup: comp.lang.c++

    On 09/12/2025 05:17, Andrey Tarasevich wrote:
    On Mon 12/8/2025 9:33 AM, Vir Campestris wrote:

    OK, I think I have it. I don't like it, but I understand.

    https://en.cppreference.com/w/cpp/language/dependent_name.html

    includes inter alia

    "a type used in a non-dependent name is incomplete at the point of
    definition but complete at the point of instantiation"

    as one of the reasons why it won't work.


    I'm not sure what kind of "reasons" you are referring to.

    The immediate reason (i.e. "because the standard says so") is the rule I have already mentioned, linked and illustrated previously: dependent
    base classes are not inspected when performing into unqualified name
    lookup. (I see that isocpp.org is back online.)

    As for rationale-kind of reason (i.e. "why does standard say so?"), it
    is quite different. It is related to the fact in many template contexts
    the compiler need to know whether a given dependent name is a type name,
    a template name or a function/variable name. This is why we sometime
    have to explicitly spell-out such keywords as `typename` or `template`
    in template contexts to disambiguate the nature of some dependent names.

    A system for this kind of disambiguation exists in C++ for _qualified_ names, but there's no such system for _unqualified_ names. Instead of expanding this system to cover unqualified names, the language simply decided to take the easy way out: postulate that names inherited from dependent base classes are "invisible" to unqualified name lookup. If
    you want to access such names, refer to them through their _qualified_ names, thus engaging _qualified_ lookup and all the rules it brings with
    it.


    I can't answer for Andy, but that explanation was a small lightbulb
    moment for me in understanding this. I always prefer to know "why does
    the standard say so?". So thank you for that post!



    --- Synchronet 3.21a-Linux NewsLink 1.2