如何确保constexpr函数从未在运行时调用?

时间:2016-09-22 23:00:02

标签: c++ c++11 c++14 constexpr

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

现在假设您在C ++中使用此函数作为constexpr,并根据某些信息(例如,内部版本号,时间戳,其他内容)为您的构建生成密钥。

你是一个勤奋的程序员,请确保以适当的方式调用它,以确保它只在编译时调用,因此死剥离器从最终的可执行文件中删除代码。

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

或者,除了安全性之外,假设该函数需要很长时间才能执行,并且您希望确保它在运行时期间永远不会发生,并为最终用户带来糟糕的用户体验。

有没有办法确保在运行时永远不会调用constexpr函数?或者,在运行时抛出一个断言或类似的东西也没关系,但不像编译错误那样理想。

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

远程相关的问题:Force constexpr to be evaluated at compile time

5 个答案:

答案 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); } 方法仅在编译时进行评估。

事实上,升职人员设法做compile time parser这一事实是一个强烈的信号,尽管这种做法虽然乏味,但这种做法应该是可行的 - 这是一次性的成本,也许人们可以认为这是一项投资

例如:

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