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

Ala*_*lfe 16 c++ constexpr c++11 c++14

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

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

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

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

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

有没有办法确保在运行时永远不会调用constexpr函数?或者,在运行时抛出一个断言或类似的东西就可以了,但不像编译错误那样明显.

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

远程相关的问题:强制constexpr在编译时进行评估

sky*_*ack 13

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

#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>>();
}
Run Code Online (Sandbox Code Playgroud)

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

  • ct可以用C++ 11中的std :: integral_constant替换.请参见http://en.cppreference.com/w/cpp/types/integral_constant (3认同)
  • @AlanWolfe 这样就不能在运行时调用它,在编译时会出错。当然,您只需要将细节隐藏在定义良好的界面后面。 (2认同)

mag*_*ras 7

在即将到来的 C++20 中,会有说明consteval.

consteval - 指定函数是立即函数,即每次调用该函数都必须产生一个编译时常量


Adr*_*chi 6

一个理论上的解决方案(如模板应图灵完整) -不使用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);
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

struct保证只在编译时对该方法进行评估.

鼓励人员设法编写编译时解析器的事实是一个强烈的信号,虽然这很乏味,但这种方法应该是可行的 - 这是一次性成本,也许人们可以认为这是一项投资.


例如:

为结构提供动力:

// ***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;
};
Run Code Online (Sandbox Code Playgroud)

构建令牌

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
  ;
}
Run Code Online (Sandbox Code Playgroud)

  • 一个.`0!= 1`.湾 "1!"的专业化是多余的. (3认同)

ipi*_*pid 5

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

template <auto V>
struct constant {
    constexpr static decltype(V) value = V;
};
Run Code Online (Sandbox Code Playgroud)

关键是非类型参数可以声明为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;
}
Run Code Online (Sandbox Code Playgroud)

  • 这里不需要 `struct`,你可以做 `template &lt;auto V&gt; 内联 constexpr auto value = V;` 并抛弃 `::value`。 (4认同)