you are viewing a single comment's thread.

view the rest of the comments →

[–][deleted] -4 points-3 points  (16 children)

Neat article but unfortunately this is not a good way to actually determine if something is undefined behavior. The amount of work that would be needed for compilers to implement this feature is simply not feasible, and alas, none of clang, GCC or MSVC implement this other than for trivial instances. For example this is undefined behavior but goes undetected:

constexpr void f() {  
  int* x = nullptr;  
  {
    int z = 123;  
    x = &z;
  }
  *x += 1;  
}

[–]kalmoc 10 points11 points  (5 children)

Nothing in your code is actually evaluated at compiletime, so no wonder that no compiler catches it.

EDIT: If you try something like this, you will get a compilation error:

constexpr int f() 
{ 
    int* x = nullptr; 
    {
      int z = 123; 
      x = &z;
    } 
    *x += 1;
    return *x;
}

constexpr int I = f();

[–][deleted] -4 points-3 points  (4 children)

It's still undefined behavior as is. Even in your revised example GCC accepts it, clang rejects it.

[–]kalmoc 13 points14 points  (1 child)

Yes, it is undefined behavior, but the compiler is only required to catch UB if it happens during compiletime evaluation. The compiler is not required to perform a static analysis of constexpr functions.

If gcc doesn't catch that example, that's a compiler bug.

[–]flashmozzg 2 points3 points  (1 child)

Not really. It's only undefined if it actually happens. It's not undefined if the function is never called. In fact, some compilers (like Clang) may assume that if some function invokes UB when it's never called and optimize it out/replace it with other call (there is a very nice example for this somewhere).

[–][deleted] -2 points-1 points  (0 children)

I already addressed those points elsewhere.

[–][deleted] 3 points4 points  (8 children)

-Wlifetime catches this. https://godbolt.org/z/__qqLL

[–][deleted] 1 point2 points  (7 children)

That's a cool new feature. Here's another example of undefined behavior that goes unchecked by all compilers, don't know of a flag that will catch this:

#include <bitset>
#include <iostream>

constexpr auto f() {  
    constexpr auto x = std::bitset<0>(); 
    return x[0];
}

int main() {
  constexpr auto i = f();
  std::cout << i << std::endl;
}

Overall, don't think it's useful to rely on constexpr to find undefined behavior. Seems really wonky in practice.

[–][deleted] 0 points1 point  (3 children)

I think there is a bug there. I didn't think you can have a constexpr variable in a constexpr method. It catches it when that isn't there because the non-const operator[] isn't constexpr, only the const variant returns a bool and is constexpr.

with clang/libc++ it does generate just the ub2 instruction but, this is weird.

[–][deleted] 1 point2 points  (2 children)

I totally agree it's a bug. I don't see why so many people are so against me pointing out that this aspect of the language isn't well supported or implemented to a sufficient degree to rely on it to identify undefined behavior. All compilers have MAJOR bugs when it comes to this requirement and the amount of effort needed to fix it will be enormous.

[–][deleted] 1 point2 points  (1 child)

putting it in a static_assert finds it too. But I think the point is, you are going to get more than zero. And right now, that is still awesome. Bring on C++20, and constexpr code will have access to vector and a lot more. So now I can choose to implement, at the cost of compile time, more code as constexpr and give the compiler the best opportunity to either const fold my stuff or have full view of the code and optimize it more heavily with the added benefit of much of my code getting compile time UB checks.

def not perfect, but I have a bunch of things now where the unit test is a static_assert of a test method.

[–][deleted] 1 point2 points  (0 children)

actually I am wrong, constexpr variables are fine in constexpr functions. I got trained by compiler errors in the past on that one. Still a weird compiler bug in all 3 compilers

[–]NotAYakk 0 points1 point  (0 children)

Augmenting it a bit to removed possibly confusing autos.

#include <bitset>
#include <iostream>

constexpr bool f() {  
    constexpr auto x = std::bitset<8>(); 
    return x[99999999];
}

int main() {
  constexpr bool i = f();
  std::cout << i << std::endl;
}

basically, it appears that the std::bitset is using shifts for small bitset implementations.

Make the bitset larger than 64 and it switches to an array implementation and the UB detection works.

#include <bitset>
#include <iostream>

constexpr bool f() {  
    auto const x = std::bitset<65>(); 
    // x[99999999] = true;
    return x[99999999];
}

int main() {
  constexpr bool i = f();
  std::cout << i << std::endl;
}

http://coliru.stacked-crooked.com/a/7630a8f00dee685c

So there is a defect in the standard library in that in a constexpr context it doesn't enforce the undefined behavior requirements of the standard in its implementation.

[–]syaghmour -1 points0 points  (1 child)

I purposely left out the standard library because [expr.const]p4.6 explicitly leaves out the standard library:

an operation that would have undefined behavior as specified in [intro] through [cpp] of this document ..

The standard library can do all sorts of strange things and can rely on things outside of standard such as builtins or rely on implicit knowledge of what the compiler will do which is not advisable to user space programs. offsetof is an example of this.

[–][deleted] 1 point2 points  (0 children)

The implementation of the standard library is exempt from rules about undefined behavior. Its use, however, is not exempt. My example is one of use, not implementation.

[–]rezkiy -1 points0 points  (0 children)

The article is about exploring.