C 17 Рекурсивная структура метапрограммирования: перечисление или constexpr

#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 .