Статические целочисленные константы шаблона C++: вне определения класса
этот вопрос касается связи между шаблонами и статическими интегральными константами в Visual Studio C++ 2013 с флагом / Za. Это имеет последствия для библиотеки boost.
во-первых, давайте проверим код без шаблонов:
struct easy
{
static const int a = 5;
const int b;
easy( int b_ ) : b( std::max( b_, a ) )
{}
};
const int easy::a;
int main()
{
easy d_Easy( 0 );
return 0;
}
согласно странице руководства для параметр компилятора / Za:"в соответствии со стандартом (/Za) вы должны сделать определение вне класса для членов данных". Пример на этой странице и приведенный выше код объявляют статическая константа внутри класса и определяет ее значение там. Необходимость определения вне класса объясняется в этой ссылке.
теперь, давайте посмотрим на проблему с шаблонами.
template< class T >
struct problem
{
static const int a = 5;
const int b;
problem( int b_ ) : b( std::max( b_, a ) )
{}
};
template< class T >
const int problem< T >::a;
int main()
{
problem< char > d_Bad( 666 );
return 0;
}
при компиляции с /Za компоновщик выдает ошибку "LNK2019: неразрешенный внешний символ". Эта ошибка не отображается с параметром / Ze.Основная проблема заключается в том, что некоторые библиотеки boost используют BOOST_STATIC_CONSTANT и BOOST_NO_INCLASS_MEMBER_INITIALIZATION в коде, аналогичном приведенному выше снипету.
взлом некоторые:
template< class T >
struct fixed
{
static const int a;
const int b;
fixed( int b_ ) : b( std::max( b_, a ) )
{}
};
template< class T >
const int fixed< T >::a = 5;
int main()
{
fixed< char > d_Good( 777 );
return 0;
}
этот код теперь компилируется с /Za.
вопросы:
1) что говорит стандарт C++11 о шаблонах и статических интегральных константах? Могут / должны ли они иметь определение вне класса, но их значение должно быть указано в определении класса?
2) у boost есть некоторые обходные пути?
обновление
важно сохранить std::max
в коде, потому что (я думаю) он пытается получить ссылку на свои параметры. Если использовать b_<a
затем компилятор просто оптимизирует эти константы.
2 ответов
прежде всего, объявление статического члена данных в классе никогда не является определением. если вы odr-использовать эту переменную, определение должно присутствовать - вне класса, конечно.
std::max
действительно ли odr-use a
, поскольку его параметры являются ссылками, а переменные используются odr, если ссылка привязана к ним ([basic.защита.odr] / 3). (Это действительно проблема с max
- он не должен использовать odr a
, действительно.)
В ответе @sehe он использует тернарный оператор напрямую, избегая использования odr в качестве преобразования lvalue в rvalue, немедленно применяется и дает постоянное выражение.
-
это довольно просто. Когда требуется определение статического члена данных шаблона класса, т. е. когда этот член используется odr, как в вашем случае, создается экземпляр определения (область пространства имен). [температура.ин-т]/2:
если член шаблона класса или шаблона член явно экземпляр или явно специализированный,специализация члена неявно создается экземпляр, когда специализация ссылка в контексте, который требует, чтобы определение члена существовало; в частности, инициализация (и любые связанные с ней побочные эффекты) статического элемента данных не происходит, если статический элемент данных сам используется таким образом, что требует определения статического член данных для существования.
и определение делается точно так же, как вы это сделали. [температура.static] / 1:
определение статического элемента данных или шаблона статического элемента данных может предоставляться в области пространства имен, заключающей определение шаблон класса статического члена.
[ пример:
template<class T> class X { static T s; }; template<class T> T X<T>::s = 0;
инициализатор может быть предоставлен при объявлении в классе, когда член имеет
const
интегральный тип, но это не влияет на семантику ODR в этом отношении. Определение по-прежнему требуется таким же образом и написано так же, как вы это сделали.
следовательно, кажется, что вы видите только ошибку VC++.
обходной путь, который я использовал в течение длительного времени и недавно стал более полезным в c++11:
struct easy
{
enum : int { a = 5 };
int b;
constexpr easy(int b_) : b(b_<a? a : b_)
{}
};
это стало более полезным, потому что теперь вы можете указать базовый тип:
struct Container
{
enum special_position : size_t { npos = size_t(-1), at_bof = 0 };
};
конечно, он ограничен (определяемыми пользователем/примитивными) интегральными типами.
внешние константы могут иметь то преимущество, что они могут быть фактически изменены только путем перекомпиляции единицы перевода, которая определяет значение.