如何确保在运行时永远不会调用 constexpr 函数?
How to ensure constexpr function never called at runtime?
假设您有一个函数可以为您的应用程序生成一些安全令牌,例如一些散列盐,或者可能是对称或非对称密钥。
现在假设您在 C++ 中将此函数作为 constexpr,并且您根据某些信息(例如,内部版本号、时间戳等)为您的构建生成密钥。
你是一个勤奋的程序员,确保以适当的方式调用它,以确保它只在编译时调用,因此 dead stripper 会从最终的可执行文件中删除代码。
但是,您永远无法确定其他人不会以不安全的方式调用它,或者编译器可能不会删除该函数,然后您的安全令牌算法将变为public 知识,使攻击者更容易猜测未来的令牌。
或者,除了安全性之外,假设该函数需要很长时间才能执行,并且您想确保它不会在运行时发生并给最终用户带来糟糕的用户体验。
有什么方法可以确保在运行时永远不会调用 constexpr 函数?或者,在运行时抛出一个断言或类似的东西是可以的,但显然不如编译错误那么理想。
我听说有一些方法涉及抛出一个不存在的异常类型,所以如果 constexpr 函数没有被删除,你会得到一个链接器错误,但听说这只是适用于某些编译器。
远距离相关问题:Force constexpr to be evaluated at compile time
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 的人设法做到了 compile time parser 这一事实是一个强烈的信号,表明这种方法虽然乏味,但应该是可行的 - 这是一次性成本,也许可以将其视为一项投资。
例如:
为结构供电:
// ***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 中,您只需将 constexpr
替换为 consteval
即可强制函数始终在编译时求值。
示例:
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);
}
C++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>>();
}
通过将函数的结果用作模板参数,如果无法在编译时解决,则会出现错误。
因为现在我们有了 C++17,所以有一个更简单的解决方案:
template <auto V>
struct constant {
constexpr static decltype(V) value = V;
};
关键是非类型参数可以声明为auto
。如果您使用的是 C++17 之前的标准,则可能必须使用 std::integral_constant
。还有 a proposal 关于 constant
助手 class.
一个例子:
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;
}
在即将到来的 C++20 中会有 consteval
specifier.
consteval - specifies that a function is an immediate function, that is, every call to the function must produce a compile-time constant
假设您有一个函数可以为您的应用程序生成一些安全令牌,例如一些散列盐,或者可能是对称或非对称密钥。
现在假设您在 C++ 中将此函数作为 constexpr,并且您根据某些信息(例如,内部版本号、时间戳等)为您的构建生成密钥。
你是一个勤奋的程序员,确保以适当的方式调用它,以确保它只在编译时调用,因此 dead stripper 会从最终的可执行文件中删除代码。
但是,您永远无法确定其他人不会以不安全的方式调用它,或者编译器可能不会删除该函数,然后您的安全令牌算法将变为public 知识,使攻击者更容易猜测未来的令牌。
或者,除了安全性之外,假设该函数需要很长时间才能执行,并且您想确保它不会在运行时发生并给最终用户带来糟糕的用户体验。
有什么方法可以确保在运行时永远不会调用 constexpr 函数?或者,在运行时抛出一个断言或类似的东西是可以的,但显然不如编译错误那么理想。
我听说有一些方法涉及抛出一个不存在的异常类型,所以如果 constexpr 函数没有被删除,你会得到一个链接器错误,但听说这只是适用于某些编译器。
远距离相关问题:Force constexpr to be evaluated at compile time
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 的人设法做到了 compile time parser 这一事实是一个强烈的信号,表明这种方法虽然乏味,但应该是可行的 - 这是一次性成本,也许可以将其视为一项投资。
例如:
为结构供电:
// ***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 中,您只需将 constexpr
替换为 consteval
即可强制函数始终在编译时求值。
示例:
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);
}
C++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>>();
}
通过将函数的结果用作模板参数,如果无法在编译时解决,则会出现错误。
因为现在我们有了 C++17,所以有一个更简单的解决方案:
template <auto V>
struct constant {
constexpr static decltype(V) value = V;
};
关键是非类型参数可以声明为auto
。如果您使用的是 C++17 之前的标准,则可能必须使用 std::integral_constant
。还有 a proposal 关于 constant
助手 class.
一个例子:
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;
}
在即将到来的 C++20 中会有 consteval
specifier.
consteval - specifies that a function is an immediate function, that is, every call to the function must produce a compile-time constant