Подтвердить что ты не робот

Как обеспечить, чтобы функция constexpr никогда не вызывалась во время выполнения?

Предположим, что у вас есть функция, которая генерирует токен безопасности для вашего приложения, такой как некоторая соль хэшей, или, может быть, симметричный или асимметричный ключ.

Теперь скажем, что у вас есть эта функция в С++ как constexpr и что вы создаете ключи для своей сборки на основе некоторой информации (например, номер сборки, отметка времени, что-то еще).

Вы, будучи добросовестным программистом, удостоверяетесь и вызываете это соответствующим образом, чтобы убедиться, что он вызван только во время компиляции, и, таким образом, мертвый стриппер удаляет код из окончательного исполняемого файла.

Тем не менее, вы никогда не можете быть уверены, что кто-то другой не будет называть его небезопасным способом, или что, возможно, компилятор не отключит эту функцию, а затем ваш алгоритм маркера безопасности станет общедоступным, что делает его более легким, чтобы нападавшие угадали будущие токены.

Или, в безопасности, скажем, что функция занимает много времени, и вы хотите удостовериться, что она никогда не происходит во время выполнения и вызывает плохой пользовательский интерфейс для ваших конечных пользователей.

Есть ли способ гарантировать, что функция constexpr никогда не будет вызвана во время выполнения? Или поочередно, бросать утверждение или подобное во время выполнения было бы хорошо, но не так идеально, как ошибка компиляции.

Я слышал, что существует какой-то способ включения типа исключения, которого не существует, так что если функция constexpr не будет отключена, вы получите ошибку компоновщика, но слышали, что это работает только некоторые компиляторы.

Дистанционно связанный вопрос: Принудительно constexpr оценивается во время компиляции

4b9b3361

Ответ 1

Вы можете принудительно использовать его в постоянном выражении:

#include<utility>

template<typename T, T V>
constexpr auto ct() { return V; }

template<typename T>
constexpr auto func() {
    return ct<decltype(std::declval<T>().value()), T{}.value()>();
}

template<typename T>
struct S {
    constexpr S() {}
    constexpr T value() { return T{}; }
};

template<typename T>
struct U {
    U() {}
    T value() { return T{}; }
};

int main() {
    func<S<int>>();
    // won't work
    //func<U<int>>();
}

Используя результат функции как аргумент шаблона, вы получили сообщение об ошибке, если оно не может быть разрешено во время компиляции.

Ответ 2

A Теоретическое решение (поскольку шаблоны должны быть полностью заполнены Тьюрингом) - не используйте функции constexpr и не возвращайтесь к старому стилю std=c++0x, используя исключительно struct template with values. Например, не делайте

constexpr uintmax_t fact(uint n) {
  return n>1 ? n*fact(n-1) : (n==1 ? 1 : 0);
}

но

template <uint N> struct fact {
  uintmax_t value=N*fact<N-1>::value;
}
template <> struct fact<1>
  uintmax_t value=1;
}
template <> struct fact<0>
  uintmax_t value=0;
}

Подход struct гарантированно оценивается исключительно во время компиляции.

Тот факт, что ребята из boost смогли сделать компилятор времени анализа, является сильным сигналом, который, хотя и утомительный, должен быть осуществимым - это разовая стоимость, возможно, можно считать ее инвестиционной.


Например:

в power struct:

// ***Warning: note the unusual order of (power, base) for the parameters
// *** due to the default val for the base
template <unsigned long exponent, std::uintmax_t base=10>
struct pow_struct
{
private:
  static constexpr uintmax_t at_half_pow=pow_struct<exponent / 2, base>::value;
public:
  static constexpr uintmax_t value=
      at_half_pow*at_half_pow*(exponent % 2 ? base : 1)
  ;
};

// not necessary, but will cut the recursion one step
template <std::uintmax_t base>
struct pow_struct<1, base>
{
  static constexpr uintmax_t value=base;
};


template <std::uintmax_t base>
struct pow_struct<0,base>
{
  static constexpr uintmax_t value=1;
};

Ток сборки

template <uint vmajor, uint vminor, uint build>
struct build_token {
  constexpr uintmax_t value=
       vmajor*pow_struct<9>::value 
     + vminor*pow_struct<6>::value 
     + build_number
  ;
}

Ответ 3

Поскольку теперь у нас есть С++ 17, есть более простое решение:

template <auto V>
struct constant {
    constexpr static decltype(V) value = V;
};

Ключ в том, что нетипичные аргументы могут быть объявлены как auto. Если вы используете стандарты до С++ 17, возможно, вам придется использовать std::integral_constant. Есть также предложение о классе constant помощника.

Пример:

template <auto V>
struct constant {
    constexpr static decltype(V) value = V;
};

constexpr uint64_t factorial(int n) {
    if (n <= 0) {
        return 1;
    }
    return n * factorial(n - 1);
}

int main() {
    std::cout << "10! = " << constant<factorial(20)>::value << std::endl;
    return 0;
}