Я пытаюсь определить статическую переменную шаблонного класса вне области действия класса, используя clang:
class Bar
{
public:
float a;
};
template<long count>
class Foo {
public:
static Bar* test;
};
template<long count>
decltype(Foo<count>::test) Foo<count>::test; // error
int main() {
Foo<5> f;
return 0;
}
Но я получаю следующую ошибку:
error: redefinition of 'test' with a different type: 'decltype(Foo<count>::test)' vs 'Bar *'
Мне кажется, что decltype(Foo<count>::test) должно оцениваться как Bar *.
Этот код отлично работает на MSVC.
Мой вопрос: можно ли как-то заставить decltype правильно определить тип здесь?
В реальном коде decltype определяется в макросе, который имеет некоторые дополнительные ключевые слова в зависимости от используемой конфигурации, поэтому я хотел бы, чтобы это работало, все еще используя decltype.
MSVC обычно имеет множество расширений для размещения нестандартных форм кода, которые иначе нельзя перенести.
Фактический код довольно сложен, и мы бы хотели, чтобы он был как можно ближе к оригиналу. Если это действительно то, что просто невозможно с clang, нам, возможно, придется поискать альтернативы (например, typeof отлично работает)
Обратите внимание, что msvc также жалуется в режиме C++ 20 Демо: «C2040: 'Foo<count>::test': 'unknown-type' отличается уровнями косвенности от 'Bar *'"
Определение на основе decltype(Foo<count>::test) компилируется без предупреждения с использованием g++ -Werror -Wall -Wextra и работает как положено.
Разве вы не можете использовать typedef в Foo: Demo
Подход typedef выглядит хорошо. С добавлением статического утверждения он должен гарантировать, что исходный код останется неизменным.
static_assert(std::is_same_v<Foo<42>::type, decltype(Foo<42>::test)>); имеет лучшую инкапсуляцию и может лучше соответствовать условиям вашей кодовой базы.





Этот код некорректен, потому что decltype(Foo<count>::test) обозначает уникальный тип, хотя он и эквивалентен Bar*.
Соответствующие стандартные разделы:
Два объявления соответствуют друг другу, если они (повторно) вводят одно и то же имя, оба объявляют конструкторы или оба объявляют деструкторы, [...]
Нам даже показывают пример того, что объявления не обязательно должны быть символически идентичными, чтобы соответствовать:
typedef int Int; /* ... */ void f(int); // #1 void f(Int) {} // defines #1
- basic.scope.scope - пример 2
Однако decltype особенный в этом отношении:
Если выражение
eявляется зависимым от типа,decltype(e)обозначает уникальный зависимый тип. Два таких спецификатора decltype относятся к одному и тому же типу, только если их выражения эквивалентны ([temp.over.link]).
В вашем примере:
// variable type declared as Bar* elsewhere
template<long count>
decltype(Foo<count>::test) Foo<count>::test;
decltype(Foo<count>::test) зависит от count, поэтому тип уникален и отличается от Bar*.
Доказать, что они одинаковы для произвольных выражений, в общем случае неразрешимо, поэтому логично, что компиляторы это запрещают.
GCC ложно компилирует ваш пример (см. Compiler Explorer), а clang и MSVC — нет.
Вероятно, это связано с тем, что GCC не рассматривает test как зависимый, поскольку он объявлен с типом Bar*, который не является зависимым.
Однако это не соответствует.
Ваш лучший способ действий — найти способ определить это вне очереди с тем же типом:
template<long count>
Bar* Foo<count>::test;
Если вы не можете использовать Bar* напрямую, возможно, есть другой способ сделать так, чтобы использование decltype не зависело от count.
В качестве альтернативы вы также можете определить член staticinline (начиная с C++17):
template<long count>
class Foo {
public:
static inline Bar* test = ...;
};
Примечание. Я лично столкнулся с похожей проблемой, когда компилятор не может сопоставить внестрочные определения функций-членов с объявлениями в классе.
Дополнительные примеры и обсуждение в этом вопросе: stackoverflow.com/questions/44294743/…
Но является ли Foo<count>::test зависимым именем? Полное имя является зависимым, если его контекст поиска является зависимым и не является текущим экземпляром ([temp.dep.type]/5.2). Но Foo<count> — это текущий экземпляр ([temp.dep.type]/1.2). Так что, казалось бы, Foo<count>::test здесь не зависимое имя, и правило о decltype не применяется.
@BrianBi Foo<count> не является текущим экземпляром. идентификатор шаблона может быть текущим экземпляром только в том случае, если он находится в определении шаблона класса. (См. [temp.dep.type]§1]). Если бы это был текущий экземпляр, нам также было бы разрешено писать Foo, и это относилось бы к Foo<count>.
@JanSchultke пуля 1.2 говорит: «в определении шаблона основного класса или члена шаблона основного класса»
@BrianBi мы еще не в определении члена, по крайней мере, пока мы не проанализируем имя объявления Foo<count>::test. По этой причине вы можете ссылаться только на текущий экземпляр Foo непосредственно в списках параметров функций-членов, конечных возвращаемых типах, инициализирующем выражении членов данных и т. д. При указании возвращаемого типа функции-члена или типа члена данных , это невозможно, потому что нет текущего экземпляра.
@JanSchultke Мне было бы интересно увидеть обоснование этого утверждения, основанное на нормативной формулировке. Обратите внимание, что имя внедренного класса Foo можно найти только путем поиска по имени после того, как будет виден идентификатор декларатора; [базовый.scope.класс]/1
@BrianBi Я начинаю думать, что технически вы правы или, по крайней мере, что стандарт в этом отношении недостаточно конкретизирован. Однако ни один компилятор не реализует это таким образом, отчасти потому, что требуется произвольный упреждающий синтаксический анализ, чтобы узнать, что тип элемента данных или возвращаемый тип функции-члена просматривается Foo<count> вместо глобальной области. Даже GCC не скомпилирует этот код, когда тип Foo<count>::test зависит от count, только когда он независим, как Bar* или int.
Почему нельзя указать
Bar *в качестве типа вместоdecltype(Foo<count>::test)в определении статической переменнойtest?