C++ 如何确保在运行时从不调用constexpr函数?

C++ 如何确保在运行时从不调用constexpr函数?,c++,c++11,c++14,constexpr,C++,C++11,C++14,Constexpr,假设您有一个函数,它为您的应用程序生成一些安全令牌,例如一些哈希盐,或者可能是一个对称或非对称密钥 现在让我们假设C++中的这个函数是一个CONTXPR,并且您可以根据一些信息生成生成的密钥(例如,构建号、时间戳、其他东西)。 作为一名勤奋的程序员,请确保以适当的方式调用它,以确保只在编译时调用它,这样死掉的剥离器就会从最终的可执行文件中删除代码 但是,您永远无法确定其他人不会以不安全的方式调用它,或者编译器可能不会删除该函数,然后您的安全令牌算法将成为公共知识,从而使攻击者更容易猜测未来的令牌

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

现在让我们假设C++中的这个函数是一个CONTXPR,并且您可以根据一些信息生成生成的密钥(例如,构建号、时间戳、其他东西)。 作为一名勤奋的程序员,请确保以适当的方式调用它,以确保只在编译时调用它,这样死掉的剥离器就会从最终的可执行文件中删除代码

但是,您永远无法确定其他人不会以不安全的方式调用它,或者编译器可能不会删除该函数,然后您的安全令牌算法将成为公共知识,从而使攻击者更容易猜测未来的令牌

或者,撇开安全性不谈,假设函数需要很长时间才能执行,您希望确保它不会在运行时发生,并且不会给最终用户带来糟糕的用户体验

有没有办法确保constexpr函数永远不能在运行时调用?或者,在运行时抛出断言或类似的东西也可以,但显然不如编译错误那么理想

我听说有一种方法涉及抛出一个不存在的异常类型,因此如果constexpr函数不是deadstripped out,您将得到一个链接器错误,但听说这只在某些编译器上有效


远近相关的问题:

A理论解决方案(因为模板应该是图灵完整的)-不要使用constexpr函数,而是回到旧的
std=c++0x
计算风格,只使用带值的
结构模板。例如,不要这样做

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

构建令牌

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
  ;
}
模板
结构构建令牌{
constexpr uintmax_t值=
vmajor*pow_结构::值
+vminor*pow_结构::值
+建筑编号
;
}

A理论解决方案(因为模板应该是图灵完整的)-不要使用constepr函数,而是使用旧的
std=c++0x
计算风格,只使用带值的
结构模板。例如,不要这样做

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

构建令牌

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
  ;
}
模板
结构构建令牌{
constexpr uintmax_t值=
vmajor*pow_结构::值
+vminor*pow_结构::值
+建筑编号
;
}

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

#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 auto-ct(){return V;}
模板
constexpr auto func(){
返回ct();
}
模板
结构{
constexpr S(){}
constexpr T value(){return T{};}
};
模板
结构单元{
U(){}
T value(){返回T{};}
};
int main(){
func();
//行不通
//func();
}

通过将函数的结果用作模板参数,如果在编译时无法解决,则会出现错误。

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

#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 auto-ct(){return V;}
模板
constexpr auto func(){
返回ct();
}
模板
结构{
constexpr S(){}
constexpr T value(){return T{};}
};
模板
结构单元{
U(){}
T value(){返回T{};}
};
int main(){
func();
//行不通
//func();
}

通过将函数的结果用作模板参数,如果在编译时无法解决,则会出现错误。

因为现在我们有了C++17,所以有一个更简单的解决方案:

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

因为现在我们有了C++17,所以有一个更简单的解决方案:

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

让函数采用模板参数而不是参数,并在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;
}
#包括
模板
constexpr uint64_t阶乘(){
//请注意,我们需要将lambda传递给它自己以进行递归调用
自动f=[](uint64\u t n,自动和f)->uint64\u t{
如果(n<2)返回1;
返回n*f(n-1,f);
};
返回f(N,f);
}
使用名称空间std;
int main(){

是否可以让函数采用模板参数而不是参数,并在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;
}
#包括
模板
constexpr uint64_t阶乘(){
//请注意,我们需要将lambda传递给它自己以进行递归调用
自动f=[](uint64\u t n,自动和f)->uint64\u t{
如果(n<2)返回1;
返回n*f(n-1,f);
};
返回f(N,f);
}
使用名称空间std;
int main(){

cout在即将到来的C++20中,将会有

consteval-指定一个函数是立即函数,也就是说,对该函数的每次调用都必须生成一个编译时常量


在即将到来的C++20中,将有

consteval-指定一个函数是立即函数,也就是说,对该函数的每次调用都必须生成一个编译时常量


一种可能的解决方案是:严格按照
template struct xyz{static constexpr long long value=…;}实现该函数
。不,真的,我的意思是不要使用
constexpr函数
,而是严格地在结构模板中实现计算。注意,人们普遍认为,如果知道你的算法足以使它被破坏,那么你的算法就是垃圾。这是一个很好的一般性评论,适合那些可能遇到这个问题并希望尝试一下,但FWIW我的需求与安全性无关。只需在构建系统中运行算法并将其值导出到程序中,可能更容易维护。在CMake中,这将与
config_file()一起使用
。这样,您的算法就不会出现在编译后的代码中。一个可能的解决方案是:严格按照
模板结构xyz{static constexpr long long value=…;}
。不,我的意思是不要使用
constexpr函数
,而是实现