Прочитав статью «Вычислите длину окружности», которая, в общем-то, крайне позабавила меня своим стилем, и узнав для себя кое-что новое, я стал несколько сомневаться в достаточной подробности предложенной информации. Всё-таки компиляторов довольно много, систем тоже немало, а в статье как-то навеяно Windows и Visual Studio (на правах ИМХО).
Речь пойдёт о примерах после вынесения «загадочной» константы за пределы функции. Так как правда об этом действе хуже рассказанной хитрым профессором. Примечание: я тестил всё исключительно «на имеющихся под рукой»:
- OS X 10.10 c gcc 4.9.2 и clang 3.5 / 3.6
- Ubuntu 14.10 с clang 3.5
- Windows с MinGW-w64-32 и gcc 4.9.2
Не исключаю чего-то ещё более неординарного под другими системами. Буду рад, если кто-то расскажет о них мне.
А правда ли constexpr может всё?
Учёный студент, закончив институт и придя на работу разработчиком C++ (скажем, под основные десктопные системы, Linix, OS X, Windows), узнаёт, что в компании все уже перешли на C++11 совместимые компиляторы. Обрадованный возможностью писать проще и короче, герой в одном из заголовочных файлов пишет так:
constexpr char sin_tables[4096] { /* Заполните значениями, если очень хочется */};
Примерно через час после коммита из соседнего отдела, который тестит сборки под OS X, раздаётся недоумевающий крик, что бинарнику сильно поплохело. Всё просто, в отличие от Visual C++ (определённых новых версий), ни clang (3.5, 3.6), ни gcc (4.8, 4.9) не полагаются на подобное неустановленное поведение (компилятор Microsoft отчего-то перестал видеть в constexpr обычную переменную с внутренней линковкой и сделал, впрочем, доброе дело), и мы получили дублирование нашего массива.
Пример:
a.cpp
#include <cstdio>
#include "h.h"
void printSin1() {
for (auto &e : sin_tables)
printf("%fn", e);
}
int main() {
printSin1();
printSin2();
}
b.cpp
#include <cstdio>
#include "h.h"
void printSin2() {
for (auto &e : sin_tables)
printf("%fn", e);
}
h.h
constexpr float sin_tables[4096] { /* ... */};
void printSin1();
void printSin2();
Для тех, кому лень собирать:
Если вы считаете, что виновата OS X, то спешу вас уверить, эта «фича» присутствует и в Ubuntu, и в MinGW. Это вполне рациональная особенность компилятора…
А если попробовать старый «проверенный» способ?
Вот же напасть, посчитал наш юный программист. Может, написать хак для этих пингвино-маководов. Сказано — сделано.
#if !defined(CONST_UNIQUE)
#if defined(MSVC)
#define CONST_UNIQUE constexpr
#else
#define CONST_UNIQUE extern __attribute__((weak)) constexpr
#endif
#endif
Всё работает, всё собирается. Но через пару минут из той же комнаты доносятся новые вопли: нерабочий коммит, билдбот выкинул ошибку, что же ты делаешь?! В проблеме опять же нет ничего необычного. Clang, хоть и делающий попытки реализовать всё нестандартное в gcc, полностью совместим никогда не был и вряд ли будет. Ну и потому в особых случаях использования этого атрибута нас будет ждать сюрприз (ну как же без этого).
CONST_UNIQUE int A = 137; // gcc OK, clang OK
CONST_UNIQUE int B = A+1; // gcc OK, clang OK
#include <array>
std::array<int, A> loveClang; // gcc OK, clang FAIL
./file.cpp:21:17: error: non-type template argument is not a constant expression
std::array<int, A> loveClang;
^
Так-то всё правильно, как этот человек вообще посмел написать хак, да и отправить его в транк. История умалчивает, что в особо «понимающих» случаях в MinGW может быть применён хак selectany, вместо weak, как более Windows-подобный. Однако от вышесказанной ошибки он тоже не спасёт.
И что же делать? И как же быть?
Как показывает мой личный опыт, магические константы обычно специфичны для какого-то блока или модуля программы. Засорять ими глобальное пространство имён требуется достаточно редко, а потому сейчас никто вам не запретит написать:
MyClass.h
class MyClass {
/* ... */
static constexpr float m_pi {3.14};
/* ... */
};
MyClass.cpp
constexpr float MyClass::m_pi;
Что это вполне решит вопрос с читабельностью констант, с их видимостью и запретным размножением. С линковкой, да, вопрос остаётся открытым, но это больше, чем ничего (особенно с учётом стандартности реализации). Чтобы избежать обвинений плагиата, добавляю ссылку сюда, где подобное также описано.
Впрочем, вам не кажется, что вышесказанное — это серьёзное зацикливание на очень небольшой проблеме? В реальности все и так понимают:
- что константы типа пи есть в хедерах (причём часто даже в микроконтроллерах);
- что если нужна магическая константа, она чаще бывает целой, чем дробной, а значит скорее всего оптимизируется компилятором (притянуто за уши, да);
- под что-то константное и большое не так жалко написать пару лишних строк (раньше я, например, использовал static const в классах или extern, если надо, в других местах);
- что константы с одинаковыми значениями, но разными именами (алиасами) компилятор и без weak может оптимизировать в одну в некоторых случаях.
Автор: vit9696