Using std::array and using "array" as name
Asked Answered
G

1

28

In my C++ JSON library, I recently had a regression with GCC7. I stripped down the affected code and hope to understand the error.

The code

Consider this header myclass.hpp:

#pragma once

template <typename X>
struct A
{
    struct value_t
    {
        X array;
    };

    static A array()
    {
        return A();
    }

    friend bool operator<(const A& lhs, const A& rhs) noexcept
    {
        return lhs.val.array < rhs.val.array;
    }

    value_t val = {};  
};

As you see, I used the name "array" as member variable name in struct value_t, as name of a static function. I then included the header in the following file:

#include <array>
using std::array; // note this!
#include "myclass.hpp"

int main()
{}

The problem

The code compiles with GCC6 and Clang5 (using -std=c++11), but GCC7 reports:

In file included from example.cpp:3:0:
myclass.hpp: In function 'bool operator<(const A<X>&, const A<X>&)':
myclass.hpp:19:40: error: wrong number of template arguments (1, should be 2)
         return lhs.val.array < rhs.val.array;
                                        ^~~~~
In file included from example.cpp:1:0:
/usr/local/Cellar/gcc/7.1.0/include/c++/7.1.0/array:94:12: note: provided for 'template<class _Tp, long unsigned int _Nm> struct std::array'
     struct array
            ^~~~~
make: *** [all] Error 1

It seems as if the parser reads the "array" in lhs.val.array as std::array and treats the following < as the start of a template list.

The code can be compiled if I make any of the changes below:

  • Remove the using std::array; or move it behind #include "myclass.hpp".
  • Change return lhs.val.array < rhs.val.array; to return (lhs.val.array) < rhs.val.array;.

In addition, either compiler fails if I remove the static A array() function...

My questions

  • Is the code correct in the first place? Am I allowed to use "array" as a name even if I use using std::array;?
  • If the code is correct, is this a bug in GCC7?
Gladiatorial answered 5/6, 2017 at 11:23 Comment(21)
I would chose something more descriptive than "array" for a variable name.Crespo
It looks a lot like a GCC bug, but you can easily solve it doing this: return (lhs.val.array) < rhs.val.array;. It compiles just fine with GCC7 and GCC8 as well.Shod
I'd interpret it as a gcc 7 bug. However, if its not occurring with gcc 7.1, probably, no need for a bug report.Bornstein
@Bornstein Reproducible up to GCC8 snapshot.Shod
The compilers fail if you remove the static member function?Beware
Simplified MCVE (nothing to do with std::array specifically)Phago
@Phago To enforce your thesis, if you use unordered_map as a name, it fails both with GCC6 and clang5. Therefore nothing to do with the compiler too.Shod
Here is an example that fails both with GCC6 and clang5 (still solved by enclosing everything between ( and )).Shod
Also not related to operator-overloading or friend delayed compilation... same problem with bool f( instead of friend bool operator<(, and same problem if the function body is given out-of-linePhago
Inside the function, lhs.val has type unknown specialization (since the type of lhs is a dependent type); there are a lot of restrictions on the use of unknown specialization, with violations being ill-formed NDR. I'm not yet sure if this specific code is such a violation thoughPhago
@Phago Because of the fact that it works using a couple of parenthesis, I wouldn't say it's a violation. It's probably due to some rules about name resolution or whatever in case of dependent names. An interesting question anyway.Shod
The [temp.names]/4 says that if there is a member template of the same name, and you don't use the keyword template, the compiler must assume the name refers to a non-template. But I can't find anything talking about the case where there isn't a member template of the same name, but there might be due to it being of unknown specialization. (BTW this may be why the member function array makes a difference in some compilers)Phago
@Phago Meanwhile I'm trying to find the reason for which surrounding parentheses make it works, but I'm miserly failing. :-(Shod
Well that one is simple, (x)< cannot be parsed as introducing a template (the < must immediately follow an id-expression to be a template)Phago
@Phago Yeah, I know the why. I was trying to explain it with a reference to the working draft. I'm not that dazed!! The hangover is gone with the weekend by now. :-DShod
Almost certainly a compiler bug. Compiler is performing unqualified name lookup (basic.lookup), which finds std::array first, and since it sees the <, it is interpreting lhs.val.array < as an attempted template instantiation. When in fact what should be happening is that the compiler sees the postfix expression lhs.val. as class member access (expr.ref), so that the id expression array can be interpreted as a member of the class. +1 for Microsoft this time around as it doesn't have the same issue gcc or clang do.Mattah
@Mattah Actually, the fact that Microsoft accepts something that GCC and clang reject makes me think that it's something​ to be rejected. :-DShod
Compare core issue 1835.Paedogenesis
@Phago Isn't lhs.val a member of the current instantiation?Asphodel
@Asphodel I don't think so, there isn't even an instantiation herePhago
It is not recommended to use global using statements in header files, and even worse, in front of an include statement !! Your case adds yet another very good argument against this kind of practice. What will happen if one of you cpp file has such a statement in front of an include, and another cpp doesn't? Tsk, tsk.... that's very naughty.Martinelli
P
4

I didn't have found anything that says the behavior you uncovered is OK, but I have found following, that might be asserting otherwise.

When the name of a member template specialization appears after . or -> in a postfix-expression or after a nested-name-specifier in a qualified-id, and the object expression of the postfix-expression is type-dependent or the nested-name-specifier in the qualified-id refers to a dependent type, but the name is not a member of the current instantiation (14.6.2.1), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template.

[ Example:
struct X {
template<std::size_t> X* alloc();
template<std::size_t> static X* adjust();
};
template<class T> void f(T* p) {
T* p1 = p->alloc<200>(); // ill-formed: < means less than
T* p2 = p->template alloc<200>(); // OK: < starts template argument list
T::adjust<100>(); // ill-formed: < means less than
T::template adjust<100>(); // OK: < starts template argument list
}
— end example ]

You can work around id as already suggested by putting compared elements in parentheses. It will break name array<

    return (lhs.val.array) < (rhs.val.array);

Let's simplify you code a little bit more and remove all includes that might be obscuring what is going on. I will start with original code that still doesn't compile.

#include <cstddef> // needed for size_t
//using std::array; brings following two lines into your code:
template< class T, std::size_t N >
struct array;

template <typename X>
struct A
{
    struct value_t { int array; };
    value_t val = {};  
    friend bool operator<(const A& lhs, const A& rhs) {
        return (lhs.val.array < rhs.val.array);
    }
};

And now let's move struct value_t { int array; }; outside of templated definition:

#include <cstddef> // needed for size_t
//using std::array; brings following two lines into your code:
template< class T, std::size_t N >
struct array;

struct value_t { int array; };

template <typename X>
struct A
{
    value_t val = {};  
    friend bool operator<(const A& lhs, const A& rhs) {
        return (lhs.val.array < rhs.val.array);
    }
};

So by including <array> into your code, you brought template array into your code as shown here. In version with value_t outside of template there is array<T> and member array. These are different things and thus without any conflict.
When you put value_t inside template, the compiler starts its attempts to expand what comes from that template. It tries to do that with member array which should not happen as specified in the standard.

Anyway, it looks like bug in GCC, because when it appears in expression lhs.val.array it should be treated as templated only when prefixed with keyword template lhs.val.template array<

And yes using same name in different contexts is perfectly correct unless it is one of the reserved words, which array is not. But be careful with this use of names. I find it at least confusing to use name array for variable holding single integer. The name already suggests there will be more than one.

Pleura answered 19/6, 2017 at 21:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.