Статические целочисленные константы шаблона 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, немедленно применяется и дает постоянное выражение.

  1. это довольно просто. Когда требуется определение статического члена данных шаблона класса, т. е. когда этот член используется 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:

Жить На Coliru

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

конечно, он ограничен (определяемыми пользователем/примитивными) интегральными типами.


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