Определение статических константных элементов в определении класса

Мое понимание состоит в том, что С++ позволяет определять константные члены-члены внутри класса, если он является целым типом.

Почему тогда следующий код дает мне ошибку компоновщика?

#include <algorithm>
#include <iostream>

class test
{
public:
    static const int N = 10;
};

int main()
{
    std::cout << test::N << "\n";
    std::min(9, test::N);
}

Ошибка, которую я получаю:

test.cpp:(.text+0x130): undefined reference to `test::N'
collect2: ld returned 1 exit status

Интересно, что если я прокомментирую вызов std:: min, код компилируется и ссылки просто прекрасны (хотя test:: N также ссылается на предыдущую строку).

Любая идея относительно того, что происходит?

Мой компилятор - gcc 4.4 в Linux.

Ответ 1

Мое понимание состоит в том, что С++ позволяет определять константные члены в классе, если он является целым типом.

Вы вроде как правильно. Вы можете инициализировать статические константные интегралы в объявлении класса, но это не определение.

http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=/com.ibm.xlcpp8a.doc/language/ref/cplr038.htm

Интересно, что если я прокомментирую вызов std:: min, код компилируется и ссылки просто прекрасны (даже если в предыдущей строке также упоминается test:: N).

Любая идея относительно того, что происходит?

std:: min принимает свои параметры по ссылке const. Если бы они взяли их по стоимости, у вас не было бы этой проблемы, но поскольку вам нужна ссылка, вам также нужно определение.

Здесь глава/стих:

9.4.2/4 - Если член данных static имеет тип const integer или const перечисления, его объявление в определении класса может указывать константу инициализатор, который должен быть интегральным постоянным выражением (5.19). В этом случае член может фигурировать в интегральных постоянных выражениях. Член все еще должен быть определен в области пространства имен, если он используется в программе, и определение области пространства имен не должно содержать initializer.

См. ответ Chu для возможного обхода.

Ответ 2

Пример Bjarne Stroustrup в его часто задаваемых вопросах С++ предполагает, что вы правы и вам нужно только определение, если вы берете адрес.

class AE {
    // ...
public:
    static const int c6 = 7;
    static const int c7 = 31;
};

const int AE::c7;   // definition

int f()
{
    const int* p1 = &AE::c6;    // error: c6 not an lvalue
    const int* p2 = &AE::c7;    // ok
    // ...
}

Он говорит "Вы можете взять адрес статического члена, если (и только если) он имеет определение вне класса" . Это говорит о том, что это будет работать иначе. Возможно, ваша функция min вызывает адреса как-то за кулисами.

Ответ 3

Другим способом сделать это для целых типов в любом случае является определение констант как перечислений в классе:

class test
{
public:
    enum { N = 10 };
};

Ответ 4

Не только int. Но вы не можете определить значение в объявлении класса. Если у вас есть:

class classname
{
    public:
       static int const N;
}

в файле .h, тогда вы должны:

int const classname::N = 10;

в файле .cpp.

Ответ 5

Вот еще один способ решения проблемы:

std::min(9, int(test::N));

(Я думаю, что Crazy Eddie отвечает правильно описывает, почему проблема существует.)

Ответ 6

Начиная с С++ 11 вы можете использовать:

static constexpr int N = 10;

Это теоретически все еще требует, чтобы вы определили константу в файле .cpp, но пока вы не берете адрес N очень маловероятно, что любая реализация компилятора выдаст ошибку;).

Ответ 7

С++ позволяет статическим членам константы быть определенными внутри класса

Нет, 3.1 §2 гласит:

Объявление - это определение, если не объявляет функцию без указания тела функции (8.4), содержит спецификатор extern (7.1.1) или спецификацию привязки (7.5), и ни инициализатор или функция, объявляет статический член данных в определении класса (9.4), это декларация имени класса (9.1), это непрозрачная-enum-декларация (7.2), или она представляет собой объявление typedef (7.1.3), использование-декларация (7.3.3) или директиву (7.3.4).