Элемент `static constexpr auto`, инициализированный с помощью unnamed перечисления
Я работал над проектом С++ 11 исключительно с помощью clang++-3.4
и решил скомпилировать с помощью g++-4.8.2
в случае возникновения каких-либо несоответствий в полученных ошибках. Оказалось, что g++ отклоняет некоторый код, который принимает clang++. Я уменьшил проблему до MWE, приведенного ниже.
enum { a };
template <class T>
struct foo
{
static constexpr auto value = a;
};
int main()
{
static constexpr auto r = foo<int>::value;
}
foo.cpp: 5: 23: ошибка: 'const<anonymous enum> foo<int>::value
, объявленная с использованием анонимного типа, используется, но не определена [-fpermissive]
static const auto value = A;
Я бы хотел, чтобы какая-то помощь ответила на следующие два вопроса:
-
Какой компилятор прав в своей интерпретации стандарта? Я предполагаю, что один компилятор прав в том, что он принимает или отклоняет код, а другой ошибочен.
-
Как я могу обойти эту проблему? Я не могу назвать анонимное перечисление, потому что это из сторонней библиотеки (в моем случае перечисления были Eigen::RowMajor
и Eigen::ColMajor
).
Ответы
Ответ 1
Кто виноват?
GCC неточно отклоняет ваш фрагмент, он является законным в соответствии со стандартом С++ 11 (N3337). Котировки с доказательством и объяснением находятся в конце этого сообщения.
обходной путь (A) - добавьте недостающее определение
template <class T>
struct foo {
static constexpr auto value = a;
typedef decltype(a) value_type;
};
template<class T>
constexpr typename foo<T>::value_type foo<T>::value;
обходной путь (B) - используйте базовый тип перечисления в качестве заполнителя
#include <type_traits>
template <class T>
struct foo {
static const std::underlying_type<decltype(a)>::type value = a;
};
Что говорит Стандарт? (N3337)
Как указано, фрагмент - это законный С++ 11, который можно прочитать в следующих цитируемых разделах.
Когда мы можем использовать тип без привязки?
[basic.link]p8
содержит подробные формулировки, которые описывают, когда тип "без привязки", и он указывает, что нумеруемое перечисление считается таким типом.
[basic.link]p8
также явно содержит три контекста, где такой тип нельзя использовать, но ни один из контекстов не применим к нашему использованию, поэтому мы в безопасности.
Тип без привязки не должен использоваться как тип переменной или функции с внешней связью, если
-
- объект имеет ссылку на язык C (7.5) или
- объект объявляется в неназванном пространстве имен (7.3.1) или
- объект не odr-used (3.2) или определен в той же единице перевода
Вы уверены, что можете использовать auto
в таком контексте?
Да, и это может быть подтверждено следующей цитатой:
7.1.6.4p
auto
спецификатор [dcl.spec.auto]
A auto
Тип-спецификатор также может использоваться при объявлении переменной в условии оператора выбора (6.4) или оператора итерации (6.5) в типе-спецификаторе-seq в идентификаторе нового типа или тип-идентификатор нового выражения (5.3.4), в декларации для диапазона и в объявлении статического элемента данных с помощью элемента выравнивания или равенства, который появляется в спецификации элемента определения класса (9.4.2).
Ответ 2
Какой компилятор прав в своей интерпретации стандарта?
gcc
неверно. §9.4.2/3:
Статический член данных типа literal может быть объявлен в классе определение с помощью спецификатора constexpr; если это так, то его выражение должно задайте инициализатор скобок или равный, в котором каждый параметр-инициализатор то есть выражение-присваивание является постоянным выражением. Участник все еще должны быть определены в области пространства имен, если он используется (3.2) в формате odr программа и определение области пространства имен не должны содержать инициализатор.
И имя не используется odr в соответствии с §3.2:
Переменная, имя которой отображается как потенциально оцениваемое выражение, odr-used , если это не объект, который удовлетворяет требованиям для отображения в постоянном выражении (5.19) и lvalue-to-rvalue преобразование (4.1) немедленно применяется.
Это действительно так: он удовлетворяет требованиям для появления в постоянном выражении и немедленное применение преобразования lvalue-to-rval (оно используется как инициализатор для объекта). Таким образом, отказ GCC неверен.
Возможным обходным решением является определение члена (но без типа заполнителя). Это определение достаточно для Clang и GCC:
template< typename T >
constexpr decltype(a) foo<T>::value;
Ответ 3
Обходной путь с decltype
:
enum { a };
template <class T>
struct foo
{
static constexpr auto value = a;
};
template <class T>
constexpr decltype(a) foo<T>::value;
int main()
{
static constexpr auto r = foo<int>::value;
}