#c #templates #enums #c 17 #constexpr
#c #шаблоны #перечисления #c 17 #constexpr
Вопрос:
В иллюстративных целях я показываю два небольших, немного отличающихся шаблонных рекурсивных определения. Один использует enum
, а другой использует static constexpr
для определения значения.
Я проверил сборку вывода из обеих программ, и они точно такие же, и семантически они также выглядят одинаково.
Я думаю constexpr
, что, возможно, это немного более современно, но есть ли какие-либо различия между использованием enum
/ static constexpr
или есть какие-то конкретные варианты использования, где разница действительно имела бы значение?
// using enum
template<uint64_t N>
struct Sum {
enum : uint64_t { value = N Sum<N - 1>::value };
};
template<>
struct Sum<0> {
enum : uint64_t { value = 1 };
};
// using static constexpr
template<uint64_t N>
struct Sum {
static constexpr uint64_t value = N Sum<N - 1>::value;
};
template<>
struct Sum<0> {
static constexpr uint64_t value = 1;
};
Извлечение значения:
#define sum(n) (Sum<n>::value)
Комментарии:
1. немного неясно, о чем вы спрашиваете. Вы пишете «они точно такие же», а затем спрашиваете, где это различие имеет значение
2. Что я имел в виду: программы на C отличаются, но в данном конкретном случае они генерируют один и тот же результат сборки. Итак, мне было интересно, как они / должны / могут использоваться по-разному, и что я должен иметь в виду, когда узнаю больше.
3. Хорошо, понял.
value
имеет разный тип в двух примерах, что может иметь значение.4. С самого начала,
amp;Sum<0>::value
скомпилировался бы со вторым примером, но потерпел неудачу с первым.
Ответ №1:
Наиболее существенное отличие (поскольку C 17 позволяет избежать необходимости внеклассового определения для статического элемента данных) заключается в том, что перечислители создаются вместе с содержащим классом, тогда как экземпляры статических элементов данных создаются только при необходимости. (Обратите внимание, однако, что MSVC, по крайней мере, не всегда должным образом откладывает их.)
Это имеет значение, когда у вас есть несколько таких констант, и некоторые из них имеют смысл только для определенных специализаций. Ошибки в инициализаторе, подобном T::maybe_exists
, не будут вызваны созданием экземпляра класса в случае статического элемента данных.
Ответ №2:
еще одно отличие: о ODR-используется.
как сказал @Igor Tandetnik, при использовании amp;Sum<0>::value
он будет работать только в том случае, если value
является static constexpr
переменной. это потому, value
что, является литералом для перечислителя, а не переменной. но обратите внимание, amp;value
используется ли ODR, который требует value
, чтобы где-то было одно и только одно определение. таким образом, вы должны объявить uint64_t const Sum<0>::value;
at XXX.cpp
, чтобы предоставить определение, перед C 17 ( static constexpr
переменная неявно является inline
переменной после C 17, и inline
переменной разрешено иметь более одного определения среди единиц перевода).
может быть, вы думаете: «Я вообще не буду использовать amp;value
«. но у вас возникнут проблемы в другом случае: uint64_t constamp; a = value
, который также используется для ODR. для перечислителя его время жизни будет продлено ссылкой (как инициализация ссылки). но static constexpr
переменная не будет и требует определения, как и using amp;value
. это всегда вызывает некоторые проблемы, когда вы используете какую-либо функцию, передаваемую по ссылке, например std::find
.