Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using std::array and using "array" as name

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?
like image 341
Niels Lohmann Avatar asked Jun 05 '17 11:06

Niels Lohmann


1 Answers

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.

like image 81
Marek Vitek Avatar answered Oct 03 '22 00:10

Marek Vitek