假设您有一个为您的应用程序生成一些安全令牌的函数,例如一些哈希盐,或者可能是对称或不对称的密钥。
现在假设您在C ++中使用此函数作为constexpr,并根据某些信息(例如,内部版本号,时间戳,其他内容)为您的构建生成密钥。
你是一个勤奋的程序员,请确保以适当的方式调用它,以确保它只在编译时调用,因此死剥离器从最终的可执行文件中删除代码。
但是,您无法确定其他人是否会以不安全的方式调用它,或者编译器可能不会删除该功能,然后您的安全令牌算法将成为公共知识,让攻击者更容易猜出未来的令牌。
或者,除了安全性之外,假设该函数需要很长时间才能执行,并且您希望确保它在运行时期间永远不会发生,并为最终用户带来糟糕的用户体验。
有没有办法确保在运行时永远不会调用constexpr函数?或者,在运行时抛出一个断言或类似的东西也没关系,但不像编译错误那样理想。
我听说有一些方法涉及抛出一个不存在的异常类型,所以如果constexpr函数没有被删除,你会得到一个链接器错误,但是听说这只适用于一些编译器。
答案 0 :(得分:11)
您可以在常量表达式中强制使用它:
#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>>();
}
通过将函数的结果用作模板参数,如果无法在编译时解决,则会出现错误。
答案 1 :(得分:6)
理论解决方案(因为模板应该是图灵完成的) - 不要使用constexpr函数并使用{{{{{{{{{{{{ 1}}。例如,不要做
std=c++0x
但是
struct template with values
保证constexpr uintmax_t fact(uint n) {
return n>1 ? n*fact(n-1) : (n==1 ? 1 : 0);
}
方法仅在编译时进行评估。
例如:
to power struct:
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
答案 2 :(得分:3)
自从我们有了C ++ 17以来,有一个更简单的解决方案:
template <auto V>
struct constant {
constexpr static decltype(V) value = V;
};
关键是非类型参数可以声明为auto
。如果您在C ++ 17之前使用标准,则可能必须使用std::integral_constant
。关于constant
助手类也有a proposal。
一个例子:
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;
}
答案 3 :(得分:2)
在即将到来的C ++ 20中将有consteval
specifier。
consteval-指定一个函数为立即函数,也就是说,对该函数的每次调用都必须产生一个编译时常量
答案 4 :(得分:1)
让函数使用模板参数而不是参数,并在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;
}