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 foo, semantically defined as

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

    return *parg;
}

The whole code above is used to illustrate a simple idea - function returns it's own argument unless the argument is equal to 5, in which case behavior is undefined.

Now, the challenge - modify the function in such a way, that if it's argument is known at compile time, a compiler diagnostic (warning or error) should be generated, and if not, behavior remains undefined in runtime. Solution could be compiler-dependent, as long as it is available in either one of the big 4 compilers.

Here are some potential routes which do not solve the problem:

  • Making function a template which takes it's argument as a template parameter - this doesn't solve the problem because it makes function ineligible for run-time arguments
  • Making function a constexpr - this doesn't solve the problem, because even when compilers see undefined behavior, they do not produce diagnostics in my tests - instead, gcc inserts ud2 instruction, which is not what I want.
SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • 6
    out of curiosity, why this Q is upvoted while linked-one is downvoted? – apple apple Dec 26 '18 at 15:11
  • 1
    @appleapple Because this one is #1 well-formulated, #2 gives a short a suffisant context and #3 defines a [precise (SMART) objective](https://en.wikipedia.org/wiki/SMART_criteria#Current_definitions). – YSC Dec 26 '18 at 15:14
  • @appleapple - The OP over there started it out as a C/C++ question. It... didn't do the question any favors. Though they did clean it up since. – StoryTeller - Unslander Monica Dec 26 '18 at 15:16
  • The function must be at least static else there is no way allowing the compiler to know the argument at compile time. – bruno Dec 26 '18 at 15:19
  • 6
    @YSC well, this question *does not show any research effort*; it's unclear or *not useful*. I'd give a downvote if I have to cast one. – apple apple Dec 26 '18 at 15:22
  • @appleapple please feel free to downvote if you think the question is lacking necessary qualities – SergeyA Dec 26 '18 at 15:24
  • 1
    @appleapple You don't have to cast one, but if you want to, feel free to do so. This is a matter of taste I guess. I find it useful (I never succeded at that task and I think it would be nice to have an API making a value-related contract enforced by a compiler error). – YSC Dec 26 '18 at 15:27
  • Possible duplicate of [c++ compile-time check function arguments](https://stackoverflow.com/questions/18981752/c-compile-time-check-function-arguments) – Iłya Bursov Dec 26 '18 at 15:28
  • 1
    @YSC this Q does not meat my own requirement to do a cast, so I simply leave a comment. – apple apple Dec 26 '18 at 15:29
  • 1
    @SergeyA It'd be a better Q if include some (failed) attempt and their explain IMHO. – apple apple Dec 26 '18 at 15:32
  • 1
    @appleapple I haven't attempted anything because I am out of ideas. – SergeyA Dec 26 '18 at 15:38
  • 4
    @IłyaBursov - not a duplicate, suggested answer doesn't solve the problem. – SergeyA Dec 26 '18 at 15:39
  • @SergeyA Same here. I wonder if it is actually possible or not. – Kunal Puri Dec 26 '18 at 15:50
  • If you use C++20's contracts, a sufficiently smart static analyzer should be able to detect this. For simple cases like this, it may be caught by the compiler. – Justin Dec 26 '18 at 16:10
  • @Justin if you can provide a working example, I will gladly accept! – SergeyA Dec 26 '18 at 16:13
  • @SergeyA By "working example," do you just mean the code which can hypothetically be detectable, or an actual piece of code which existing tools detect? AFAIK, no tool currently exists which can detect C++20 contract violations at compile time, especially since no compiler exists which can compile contracts. – Justin Dec 26 '18 at 16:31
  • @Justin I mean the actual demonstration. Ok, may be this question will have to be aged until 2020 :) – SergeyA Dec 26 '18 at 16:35
  • 1
    With `constexpr`, I got expected error with minor change to allow compilation [Demo](http://coliru.stacked-crooked.com/a/8880c3f1afb2c6f8). – Jarod42 Dec 26 '18 at 19:03

3 Answers3

4

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.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Constexpr version doesn't give me any diagnostic https://gcc.godbolt.org/z/ZCho3b when it's result is not used to initialize constant expression variable. – SergeyA Dec 26 '18 at 20:17
  • I do understand that, and this is exactly what I am referring to - unless the function is called in constant expression, no diagnostics are provided – SergeyA Dec 26 '18 at 20:24
  • That's why I provide in my answer way to pass compile time argument (with `int_c`) which allows to check at compile time , as they call it in constant expression. – Jarod42 Dec 26 '18 at 20:26
  • Jarod, may be I am not making myself clear enough. The goal is to have a callable function, which would enforce contract in compile time when it possible (i.e. argument is known). Your solution effectively introduces two overloads, and successful compile-time check is predicated on developers discipline to call second overload. – SergeyA Dec 26 '18 at 20:34
  • Found a way with built-in of gcc supported by clang (and so works on clang but not with g++ ;-) ) – Jarod42 Dec 26 '18 at 22:07
  • That's golden! Two small notes - you probably meant '**is**_constexpr seems not possible...' and also I believe you second half answers the question, but first does not, so may be putting the `__builtin_constant_p` solution first, and than reference other options as alternatives? Accepted regardless :) – SergeyA Dec 27 '18 at 14:19
  • Indeed, typo fixed. – Jarod42 Dec 27 '18 at 14:21
2

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)

Iłya Bursov
  • 23,342
  • 4
  • 33
  • 57
-1

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
Fureeish
  • 12,533
  • 4
  • 32
  • 62
  • 2
    No, it doesn't *work*. Behavior is dependent on programmer's discipline (making sure to provide template argument when it's known at compile time). Instead of this approach, if programmer is disciplined, I'd simply have two functions - templated and not. – SergeyA Dec 26 '18 at 15:53
  • 1
    Fair, thank you for the comment. I totally agree that this does not actually solve the *entire* problem, but I will leave it as a neutral hint / information for future visitors :> – Fureeish Dec 26 '18 at 15:54
  • You might add an additional runtime check that argument are equal or defaulted. – Jarod42 Dec 26 '18 at 22:12