如何确保constexpr函数在运行时永远不会被调用

How to ensure constexpr function never called at runtime?

本文关键字:永远 运行时 调用 函数 何确保 确保 constexpr      更新时间:2023-10-16

假设您有一个函数为您的应用程序生成一些安全令牌,例如一些散列盐,或者可能是一个对称或非对称密钥。

现在让我们假设你在c++中有这个函数作为constexpr,并且你根据一些信息(比如,构建号,时间戳,其他东西)为你的构建生成键。

作为一个勤奋的程序员,你要确保以适当的方式调用它,以确保它只在编译时被调用,因此dead stripper会从最终的可执行文件中删除代码。

然而,你永远不能确定其他人不会以不安全的方式调用它,或者编译器不会剥离函数,然后你的安全令牌算法将成为公共知识,使潜在攻击者更容易猜测未来的令牌。

或者,撇开安全不谈,假设这个函数需要很长时间来执行,而你想确保它不会在运行时发生,从而给最终用户带来糟糕的用户体验。

是否有任何方法可以确保constexpr函数永远不会在运行时被调用?或者,在运行时抛出断言或类似的错误也可以,但不像编译错误那样明显。

我听说有一些方法涉及抛出一个不存在的异常类型,所以如果constexpr函数没有死剥离,你会得到一个链接器错误,但听说这只适用于某些编译器。

远相关问题:强制constexpr在编译时求值

在c++ 20中你可以用consteval代替constexpr来强制一个函数总是在编译时求值。

的例子:

          int    rt_function(int v){ return v; }
constexpr int rt_ct_function(int v){ return v; }
consteval int    ct_function(int v){ return v; }
int main(){
    constexpr int ct_value = 1; // compile value
    int           rt_value = 2; // runtime value
    int a = rt_function(ct_value);
    int b = rt_ct_function(ct_value);
    int c = ct_function(ct_value);
    int d = rt_function(rt_value);
    int e = rt_ct_function(rt_value);
    int f = ct_function(rt_value); // ERROR: runtime value
    constexpr int g = rt_function(ct_value); // ERROR: runtime function
    constexpr int h = rt_ct_function(ct_value);
    constexpr int i = ct_function(ct_value);
}

prec ++20解决方案

你可以在常量表达式中强制使用它:

#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>>();
}

通过使用函数的结果作为模板参数,如果在编译时无法解决,则会得到错误。

一个理论解决方案(作为模板应该是图灵完备的)-不要使用constexpr函数,而只使用struct template with values返回到好的旧std=c++0x风格的计算。例如,不要做

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的家伙设法做一个编译时解析器的事实是一个强烈的信号,尽管繁琐,这种方法应该是可行的-这是一次性的成本,也许可以认为它是一种投资。


例如:

to power structure:

// ***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
  ;
}

在即将到来的c++ 20中,将有consteval说明符。

consteval——指定函数是直接函数,也就是说,每次调用该函数都必须产生一个编译时常数

现在我们有c++ 17了,有一个更简单的解决方案:

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

关键是非类型参数可以声明为auto。如果你使用c++ 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 << "20! = " << constant<factorial(20)>::value << std::endl;
    return 0;
}

让你的函数接受模板形参而不是实参,并在lambda中实现你的逻辑。

#include <iostream>
template< uint64_t N >
constexpr uint64_t factorial() {
    // note that we need to pass the lambda to itself to make the recursive call
    auto f = []( uint64_t n, auto& f ) -> uint64_t {
        if ( n < 2 ) return 1;
        return n * f( n - 1, f );
    };
    return f( N, f );
}
using namespace std;
int main() {
    cout << factorial<5>() << std::endl;
}