16

Suppose I have the following simplified program:

Link to godbolt.org:

#include <cassert>

struct Dimensions {

    Dimensions& operator=(int i) {
      assert(i != 0);
      return *this;
    }

};

int getDim();

int main() {
    Dimensions dims;
    dims = getDim();//ok, just use runtime assert
    dims = 0;//compile error wanted here
    return 0;
}

In the first case (getDim), it's not possible to check compile-time so we are happy to just check it at runtime.

But is it somehow possible to detect at compile as well (for the second case, dims = 0;), when in theory it looks like it could be possible ? (perhaps with some kind of overloading or wrapper even ?)

darune
  • 10,480
  • 2
  • 24
  • 62
  • https://en.cppreference.com/w/cpp/language/static_assert ? – george_ptr Sep 04 '19 at 09:57
  • 4
    @george_ptr Will always give a compile time error, because `i` is not usuable in a constant expression in that context. – walnut Sep 04 '19 at 09:58
  • I am certain this is impossible to do. You will sadly have to settle for something like `dims = check_value<0>();` – Stack Danny Sep 04 '19 at 10:06
  • I don't think you can do that with a non-constexpr function – L. F. Sep 04 '19 at 10:07
  • 2
    sloppy speaking, in `dims = 0;` the `0` is a runtime value. Only if you can accept to turn it into a compile time value you can statically assert on it (eg as a template param, as suggested by StackDanny) – 463035818_is_not_an_ai Sep 04 '19 at 10:10
  • Looks like you actually want some static analysis, not sure if that actually exist, although not that far-fetched – JVApen Sep 04 '19 at 10:31
  • Out of interest, how many dimensions do you need to support? You *could* engineer this with a change of syntax, like `dims = zero;` for example, but that could be viewed as silly (I wouldn't do it). – Bathsheba Sep 04 '19 at 10:40
  • 1
    @Bathsheba ty, thats a good idea, however i cannot use in the actual case and it's not even dimensions (I just thought that would convey the meaning better) – darune Sep 04 '19 at 10:55
  • @Bathsheba but if you feel like it, post it as an answer as it could help in other cases perhaps. – darune Sep 04 '19 at 10:57
  • 1
    @darune: Not sure I should - I think it's too idiosyncratic and I wouldn't do it myself, and I'm of the opinion that a runtime assertion is good enough. – Bathsheba Sep 04 '19 at 10:57
  • In this case, it is easy to see that the assert will be broken, but in general, this seems to be tough. Is this the actual thing you want to check? I'm asking because in general, in might be more fruitful to have such things done by unit tests, and maybe have them called by a script you also use for compilation, or by a deployment script, or similar. Other than that, can you work with `constexpr` at such places? – Aziuth Sep 04 '19 at 11:23
  • 2
    Wanted to make it work with `std::is_constant_evaulated()` but it acts strangely, maybe someone else will be able to fix it [godbolt](https://godbolt.org/z/Yg5Ysu). – KamilCuk Sep 04 '19 at 11:27
  • @Aziuth constexpr might be interesting to explore - unfortunately I cannot use that directly in my actual case though – darune Sep 04 '19 at 11:34

1 Answers1

8

A typical way to do in C with gcc compiler, that will also work in C++: you use __builtin_constant_p builtin to check if an expression is a constantly evaluated and then check the expression and then call a function declared with a __attribute__((__warning__)) or with __attribute__((__error__)). Like so:

#include <cassert>
#include <type_traits>

#define CONCAT(a, b) a ## b
#define XCONCAT(a, b) CONCAT(a, b)
#define maybe_static_maybe_not_assert(expr) do { \
    if (__builtin_constant_p(expr)) { \
            if (!(expr)) { \
                extern __attribute__((__warning__( \
                "static_assert: expression: " #expr " will fail on runtime!" \
                ))) void XCONCAT(maybe_static_maybe_not_assert_warn, __LINE__)(); \
              XCONCAT(maybe_static_maybe_not_assert_warn, __LINE__)(); \
         } \
    } \
    assert(expr); \
} while(0)

struct Dimensions {

    Dimensions& operator=(int i) {
        maybe_static_maybe_not_assert(i != 0);
        return *this;
    }

};

int getDim();

int main() {
    Dimensions dims;
    dims = getDim();
    dims = 0;
    dims = 1;
    return 0;
}

It should issue a warning when compiled with optimizations:

In member function 'Dimensions& Dimensions::operator=(int)',
    inlined from 'int main()' at <source>:32:12:
<source>:12:70: warning: call to 'maybe_static_maybe_not_assert_warn21' declared with attribute warning: static_assert: expression: i != 0 will fail on runtime! [-Wattribute-warning]
   12 |                 XCONCAT(maybe_static_maybe_not_assert_warn, __LINE__)(); \
      |                                                                      ^
<source>:21:9: note: in expansion of macro 'maybe_static_maybe_not_assert'
   21 |         maybe_static_maybe_not_assert(i != 0);
      |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Compiler returned: 0

This is the way _FORTIFY_SOURCE is implemented in glibc.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Is this possible with msvc as well ? (or clang ?) – darune Sep 04 '19 at 11:18
  • clang supports gnu extensions, so it should work with clang, however you have to change `__attribute__((__warning__))` to `__attribute__((__deprecated__))`. clang doesn't recognize `warning` and `error` attributes, but has a `deprecated` attribute, typically used. – KamilCuk Sep 04 '19 at 11:19
  • And I guess this is not possible with MSVC then ? and theres no plans on MS side to support it (?) – darune Sep 04 '19 at 11:24
  • 1
    I have no experience with MSVC, so I am not inclined to answer. I have never seen a way to do `__builtin_constant_p` in MSVC. – KamilCuk Sep 04 '19 at 11:28
  • 1
    @KamilCuk : how would that work on `clang` with `__attribute__((__deprecated__))` ? It seems this one triggers a warning any time the function is called in the source code, irrespective of the fact that it will be called or not, thus oblivious to DCE optimization. – Cyan Mar 16 '21 at 01:54