Подтвердить что ты не робот

Использование std:: array и использование "массива" в качестве имени

В моей библиотеке С++ JSON у меня недавно была регрессия с GCC7. Я удалил поврежденный код и надеюсь понять ошибку.

Код

Рассмотрим этот заголовок 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 = {};  
};

Как вы видите, я использовал имя "array" как имя переменной-члена в struct value_t, как имя статической функции. Затем я включил заголовок в следующий файл:

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

int main()
{}

Проблема

Код компилируется с помощью GCC6 и Clang5 (используя -std=c++11), но GCC7 сообщает:

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

Кажется, что парсер читает "массив" в lhs.val.array как std::array и рассматривает следующий < как начало списка шаблонов.

Код можно скомпилировать, если я сделаю любое из следующих изменений:

  • Удалите using std::array; или переместите его за #include "myclass.hpp".
  • Измените return lhs.val.array < rhs.val.array; на return (lhs.val.array) < rhs.val.array;.

Кроме того, сбой компилятора, если я удаляю функцию static A array()...

Мои вопросы

  • Является ли код правильным в первую очередь? Могу ли я использовать "массив" в качестве имени, даже если я использую using std::array;?
  • Если код верен, это ошибка в GCC7?
4b9b3361

Ответ 1

Я не нашел ничего, что говорит о том, что поведение, которое вы обнаружили, в порядке, но я нашел следующее, что может быть сказано иначе.

Когда после этого появится спецификация шаблона члена. или → в постфиксном выражении или после вложенного имени-спецификатора в квалифицированном-id, а выражение объекта postfix-expression - тип-зависимый или спецификатор вложенного имени в идентификаторе с квалификацией-id к зависимому типу, но имя не является членом текущего (14.6.2.1), имя шаблона члена должно иметь префикс шаблон ключевого слова. В противном случае предполагается, что имя без шаблона.

[ 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 ]

Вы можете работать с id, как уже было предложено, помещая сравниваемые элементы в круглые скобки. Это сломает имя array<

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

Давайте упростим вам код немного больше и удалим все, что может быть скрыто, что происходит. Я начну с исходного кода, который до сих пор не компилируется.

#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);
    }
};

И теперь переместите struct value_t { int array; }; вне шаблонного определения:

#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);
    }
};

Итак, включив <array> в ваш код, вы внесли шаблонный массив в свой код, как показано здесь. В версии с value_t вне шаблона есть array<T> и member array. Это разные вещи и, таким образом, без каких-либо конфликтов. Когда вы помещаете value_t внутри шаблона, компилятор начинает попытки расширить то, что исходит от этого шаблона. Он пытается сделать это с помощью массива-члена, который не должен выполняться, как указано в стандарте.

В любом случае, это похоже на ошибку в GCC, потому что, когда она появляется в выражении lhs.val.array, ее следует рассматривать как шаблонную только при префиксе с шаблоном ключевого слова lhs.val.template array<

И да, используя одно и то же имя в разных контекстах, совершенно корректно, если только это не одно из зарезервированных слов, какой массив нет. Но будьте осторожны с этим использованием имен. Я считаю, что он по меньшей мере запутывает использование массива имен для переменной, содержащей одно целое число. Название уже предполагает, что будет больше одного.