В следующих, static constexpr
член L
инициализируется в классе A
и затем передается по значению или (универсальная) ссылка. Последняя ошибка в Clang, но не в GCC, и поведение немного отличается для функций-членов / не-членов. Более подробно:
#include <iostream>
using namespace std;
struct A
{
static constexpr size_t L = 4;
template <typename T>
void member_ref(T&& x) { cout << std::forward<T>(x) << endl; }
template <typename T>
void member_val(T x) { cout << x << endl; }
};
template <typename T>
void ref(T&& x) { cout << std::forward<T>(x) << endl; }
template <typename T>
void val(T x) { cout << x << endl; }
int main ()
{
A().member_ref(A::L); // Clang: linker error: undefined reference to `A::L'
A().member_val(A::L); // fine (prints 4)
ref(A::L); // Clang: compiles/links fine, no output
val(A::L); // fine (prints 4)
}
После некоторых экспериментов по выделению проблемы из более крупной программы я понял, что случайно использую адрес constexpr
переменная, хотя меня интересует только значение.
Я хочу передать (универсальную) ссылку, чтобы код был универсальным и работал с большими структурами без копирования. Я думал, что вы можете передать что-нибудь с универсальной ссылкой, но, похоже, это не тот случай. Я не могу использовать отдельное (внеклассное) определение для L
потому что это часть библиотеки только для заголовка.
Таким образом, одним из обходных путей может быть создание значения по вызову, то есть, скажем, size_t(A::L)
или что-то вроде A::get_L()
вместо просто A::L
где (в пределах класса A
)
static constexpr size_t get_L() { return L; }
но оба решения выглядят немного неуклюже. В моем собственном коде вызов сделан внутри класса и выглядит как call(0, L, ...)
что выглядит довольно невинно (0, L
выглядят как ценности). Я хотел бы сделать звонок максимально простым.
Я думаю этот вопрос а также его продолжение в значительной степени объяснить, что происходит. Так может ли кто-нибудь предложить, какой самый чистый способ справиться с этим?
Вам нужно определить A::L
вне его класса в исходном файле
constexpr size_t A::L;
Живой пример используя Clang
Для кода только для заголовка, и если ваш класс A
это еще не шаблон, вы можете определить шаблон класса A_<T>
с void
значение по умолчанию, и напишите typedef для A
с точки зрения этого
template<class = void>
struct A_
{
static constexpr size_t L = 4;
template <typename T>
void member_ref(T&& x) { cout << std::forward<T>(x) << endl; }
template <typename T>
void member_val(T x) { cout << x << endl; }
};
template<class T>
constexpr size_t A_<T>::L;
using A = A_<>;
НОТА: этот бизнес может включать изрядное количество котельной плиты. Приятно отметить, что можно написать
template
<
class MyConcept1,
class MyConcept2,
class YetAnotherConcept
// more long and well-documented template parameter names
>
struct A
{
// many static constexpr variabels
};
template<class P1, class P2, class P3 /* many more short parameter names */>
constexpr SomeType A<P1, P2, P3, /* many more */>::some_var;
// many more one-liners.
Параметры шаблона просто имеют формальные имена, они не должны быть везде одинаковыми (просто поместите их в правильный порядок хоть везде!)
Других решений пока нет …