Remove undefined behavior from overflow of signed integers in constant expressions?

Viewed 93

EDIT In the actual example, it appears possible that negative overflow can happen, I've also added an example to demonstrate the error there

I'm using C++20 and trying to convert a library which relies on signed integer overflow in Java and C# into C++ code. I'm also trying to generate the tables it uses at compile time, and allow those to be available at compile time.

In my code I get errors in reference to code that looks like this (Minimal example to reproduce the error, the solution to this will solve my problem as well):

#include <iostream> 

constexpr auto foo(){
    std::int64_t a = 2; 
    std::int64_t very_large_constant = 0x598CD327003817B5L; 
    std::int64_t x = a * very_large_constant; 
    return x; 
}
 
int main(){
    std::cout << foo() << std::endl; 
    return 0; 
}

https://godbolt.org/z/TvM45vd8d

Negative overflow version

#include <iostream> 

constexpr auto foo(){
    std::int64_t a = -2; 
    std::int64_t very_large_constant = 0x598CD327003817B5L; 
    std::int64_t x = a * very_large_constant; 
    return x; 
}
 
int main(){
    std::cout << foo() << std::endl; 
    return 0; 
}

https://godbolt.org/z/7zoE9r18E

I get 12905529061151879018 is out side of range representable by long long and -12905529061151879018 respectively.

I understand that undefined behavior here is not allowed, I also recognize that GCC and MSVC do not error here, and you can put a flag to make clang compile this anyway. But what am I supposed to do to actually solve this issue with out switching compilers or applying the flag to ignore invalid constexpr?

Is there some way I can define the behavior I expect and want to happen here?

2 Answers

Signed integers have two's complement layout in any implementation that you could name. It's also guaranteed to use two' s complement layout since C++20.

This means that you can perform your math on unsigned integers and get well-defined overflow behavior that matches what you want your signed integers to do.

#include <iostream> 
#include <bit>

constexpr auto foo(){
    std::uint64_t a = 2; 
    std::uint64_t very_large_constant = 0x598CD327003817B5L; 
    std::uint64_t x = a * very_large_constant; 
    return static_cast<std::int64_t>(x); 
}

You cannot do this with signed integers. However, there are some things you can rely on in C++20:

  1. Unsigned integer overflow is well-defined.

  2. Signed integers are required to be represented as 2's complement.

  3. Conversions between corresponding sized and unsigned integers preserve the bitpattern.

So you can do all of your overflow-based math using explicitly unsigned types and literals, then cast them to signed values when you need to. This conversion is required to leave the bits unchanged.

Related