Enforcing function contract at compile time when possible

后端 未结 3 1227
一生所求
一生所求 2021-02-02 00:12

(this question was inspired by How can I generate a compilation error to prevent certain VALUE (not type) to go into the function?)

Let\'s say, we have a single-argument

相关标签:
3条回答
  • 2021-02-02 01:01

    It's not perfect and it requires us to use arguments in two different places, but it 'works':

    template<int N = 0>
    int foo(int arg = 0) {
        static_assert(N != 5, "N cannot be 5!");
        int* parg;
        if (arg != 5) {
            parg = &arg;
        }
    
        return *parg;
    }
    

    We can call it like so:

    foo<5>();   // does not compile
    foo(5);     // UB
    foo<5>(5);  // does not compile
    foo<5>(10); // does not compile
    foo<10>(5); // UB
    foo();      // fine
    foo<10>();  // fine
    foo(10);    // fine
    
    0 讨论(0)
  • 2021-02-02 01:11

    gcc/clang/intel compilers support __builtin_constant_p, so you can use something like that:

    template <int D>
    int foo_ub(int arg) {
        static_assert(D != 5, "error");
        int* parg = nullptr;
        if (arg != 5) {
            parg = &arg;
        }
    
        return *parg;
    }
    
    #define foo(e) foo_ub< __builtin_constant_p(e) ? e : 0 >(e)
    

    these statements produce compile time error:

    • foo(5)
    • foo(2+3)
    • constexpr int i = 5; foo(i);

    while all others - runtime segfault (or ub if no nullptr is used)

    0 讨论(0)
  • 2021-02-02 01:13

    I got error with constexpr when used in constant expression for:

    constexpr int foo(int arg) {
        int* parg = nullptr;
        if (arg != 5) {
            parg = &arg;
        }
        return *parg;
    }
    

    Demo

    We cannot know that argument value is known at compile type, but we can use type representing value with std::integral_constant

    // alias to shorten name. 
    template <int N>
    using int_c = std::integral_constant<int, N>;
    

    Possibly with UDL with operator "" _c to have 5_c, 42_c.

    and then, add overload with that:

    template <int N>
    constexpr auto foo(int_c<N>) {
        return int_c<foo(N)>{};
    }
    

    So:

    foo(int_c<42>{}); // OK
    foo(int_c<5>{}); // Fail to compile
    
    // and with previous constexpr:
    foo(5); // Runtime error, No compile time diagnostic
    constexpr auto r = foo(5); // Fail to compile
    

    As I said, arguments are not known to be constant inside the function, and is_constexpr seems not possible in standard to allow dispatch, but some compiler provide built-in for that (__builtin_constant_p), so with MACRO, we can do the dispatch:

    #define FOO(X) [&](){ \
        if constexpr (__builtin_constant_p(X)) {\
            return foo(int_c<__builtin_constant_p (X) ? X : 0>{});\
        } else {\
            return foo(X); \
        } \
    }()
    

    Demo

    Note: Cannot use foo(int_c<X>{}) directly, even in if constexpr, as there is still some syntax check.

    0 讨论(0)
提交回复
热议问题