Decltype для статической переменной в классе шаблона

Я пытаюсь определить статическую переменную шаблонного класса вне области действия класса, используя 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.

Почему нельзя указать Bar * в качестве типа вместо decltype(Foo<count>::test) в определении статической переменной test?

Hari 09.06.2023 10:28

MSVC обычно имеет множество расширений для размещения нестандартных форм кода, которые иначе нельзя перенести.

Hari 09.06.2023 10:30

Фактический код довольно сложен, и мы бы хотели, чтобы он был как можно ближе к оригиналу. Если это действительно то, что просто невозможно с clang, нам, возможно, придется поискать альтернативы (например, typeof отлично работает)

xEric_xD 09.06.2023 10:41

Обратите внимание, что msvc также жалуется в режиме C++ 20 Демо: «C2040: 'Foo<count>::test': 'unknown-type' отличается уровнями косвенности от 'Bar *'"

Jarod42 09.06.2023 11:11

Определение на основе decltype(Foo<count>::test) компилируется без предупреждения с использованием g++ -Werror -Wall -Wextra и работает как положено.

Hari 09.06.2023 11:17

Разве вы не можете использовать typedef в Foo: Demo

Jarod42 09.06.2023 11:18

Подход typedef выглядит хорошо. С добавлением статического утверждения он должен гарантировать, что исходный код останется неизменным.

xEric_xD 09.06.2023 11:26
static_assert(std::is_same_v<Foo<42>::type, decltype(Foo<42>::test)>); имеет лучшую инкапсуляцию и может лучше соответствовать условиям вашей кодовой базы.
Hari 09.06.2023 11:49
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
8
76
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Этот код некорректен, потому что decltype(Foo<count>::test) обозначает уникальный тип, хотя он и эквивалентен Bar*.

Соответствующие стандартные разделы:

Два объявления соответствуют друг другу, если они (повторно) вводят одно и то же имя, оба объявляют конструкторы или оба объявляют деструкторы, [...]

- basic.scope.scope §4

Нам даже показывают пример того, что объявления не обязательно должны быть символически идентичными, чтобы соответствовать:

typedef int Int;
/* ... */
void f(int);                    // #1
void f(Int) {}                  // defines #1

- basic.scope.scope - пример 2

Однако decltype особенный в этом отношении:

Если выражение e является зависимым от типа, decltype(e) обозначает уникальный зависимый тип. Два таких спецификатора decltype относятся к одному и тому же типу, только если их выражения эквивалентны ([temp.over.link]).

- temp.type §4

В вашем примере:

// 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/…

Hari 09.06.2023 21:35

Но является ли Foo<count>::test зависимым именем? Полное имя является зависимым, если его контекст поиска является зависимым и не является текущим экземпляром ([temp.dep.type]/5.2). Но Foo<count> — это текущий экземпляр ([temp.dep.type]/1.2). Так что, казалось бы, Foo<count>::test здесь не зависимое имя, и правило о decltype не применяется.

Brian Bi 10.06.2023 03:02

@BrianBi Foo<count> не является текущим экземпляром. идентификатор шаблона может быть текущим экземпляром только в том случае, если он находится в определении шаблона класса. (См. [temp.dep.type]§1]). Если бы это был текущий экземпляр, нам также было бы разрешено писать Foo, и это относилось бы к Foo<count>.

Jan Schultke 11.06.2023 13:22

@JanSchultke пуля 1.2 говорит: «в определении шаблона основного класса или члена шаблона основного класса»

Brian Bi 11.06.2023 16:05

@BrianBi мы еще не в определении члена, по крайней мере, пока мы не проанализируем имя объявления Foo<count>::test. По этой причине вы можете ссылаться только на текущий экземпляр Foo непосредственно в списках параметров функций-членов, конечных возвращаемых типах, инициализирующем выражении членов данных и т. д. При указании возвращаемого типа функции-члена или типа члена данных , это невозможно, потому что нет текущего экземпляра.

Jan Schultke 11.06.2023 17:31

@JanSchultke Мне было бы интересно увидеть обоснование этого утверждения, основанное на нормативной формулировке. Обратите внимание, что имя внедренного класса Foo можно найти только путем поиска по имени после того, как будет виден идентификатор декларатора; [базовый.scope.класс]/1

Brian Bi 12.06.2023 09:28

@BrianBi Я начинаю думать, что технически вы правы или, по крайней мере, что стандарт в этом отношении недостаточно конкретизирован. Однако ни один компилятор не реализует это таким образом, отчасти потому, что требуется произвольный упреждающий синтаксический анализ, чтобы узнать, что тип элемента данных или возвращаемый тип функции-члена просматривается Foo<count> вместо глобальной области. Даже GCC не скомпилирует этот код, когда тип Foo<count>::test зависит от count, только когда он независим, как Bar* или int.

Jan Schultke 12.06.2023 11:49

Другие вопросы по теме