How to ensure constexpr function never called at runtime?

后端 未结 5 1022
无人及你
无人及你 2020-12-05 18:52

Lets say that you have a function which generates some security token for your application, such as some hash salt, or maybe a symetric or asymetric key.

Now lets sa

相关标签:
5条回答
  • 2020-12-05 19:06

    You can force the use of it in a constant expression:

    #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>>();
    }
    

    By using the result of the function as a template argument, you got an error if it can't be solved at compile-time.

    0 讨论(0)
  • 2020-12-05 19:11

    Since now we have C++17, there is an easier solution:

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

    The key is that non-type arguments can be declared as auto. If you are using standards before C++17 you may have to use std::integral_constant. There is also a proposal about the constant helper class.

    An example:

    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;
    }
    
    0 讨论(0)
  • 2020-12-05 19:16

    Have your function take template parameters instead of arguments and implement your logic in a 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;
    }
    
    0 讨论(0)
  • 2020-12-05 19:17

    In the upcoming C++20 there will be consteval specifier.

    consteval - specifies that a function is an immediate function, that is, every call to the function must produce a compile-time constant

    0 讨论(0)
  • 2020-12-05 19:23

    A theoretical solution (as templates should be Turing complete) - don't use constexpr functions and fall back onto the good-old std=c++0x style of computing using exclusively struct template with values. For example, don't do

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

    but

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

    The struct approach is guaranteed to be evaluated exclusively at compile time.

    The fact the guys at boost managed to do a compile time parser is a strong signal that, albeit tedious, this approach should be feasible - it's a one-off cost, maybe one can consider it an investment.


    For example:

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

    The build token

    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
      ;
    }
    
    0 讨论(0)
提交回复
热议问题