cppfront icon indicating copy to clipboard operation
cppfront copied to clipboard

[BUG] z: u8 = x + y; does not compile

Open MatthieuHernandez opened this issue 1 year ago • 14 comments

The following code does not compile.

main.cpp2

main: () = {
    x: u8 = 1;
    y: u8 = 2;  
    z: u8 = x + y;
    std::cout << z << std::endl;
}

main.cpp1 generated

auto main() -> int{
    cpp2::u8 x {1}; 
    cpp2::u8 y {2}; 
    cpp2::u8 z {cpp2::move(x) + cpp2::move(y)}; 
    std::cout << cpp2::move(z) << std::endl;
}

Compiler error

main.cpp2: In function 'int main()':
main.cpp2:4:31: error: narrowing conversion of '(((int)cpp2::move<unsigned char&>(x)) + ((int)cpp2::move<unsigned char&>(y)))' from 'int' to 'cpp2::u8' {aka 'unsigned char'} [-Wnarrowing]

Example https://cpp2.godbolt.org/z/r6r9d8KMP

MatthieuHernandez avatar Oct 13 '24 20:10 MatthieuHernandez

That's integral promotion.

JohelEGP avatar Oct 13 '24 21:10 JohelEGP

Yes indeed, but is it a bug? It's really annoying and I can't even write this to try to correct the problem: z:= (x + y) as u8;

MatthieuHernandez avatar Oct 13 '24 22:10 MatthieuHernandez

See Type/value queries and casts. In particular, you can't use the safe as to narrow. You must use unchecked_narrow<To>(from).

JohelEGP avatar Oct 13 '24 22:10 JohelEGP

Even if it is integral promotion, the C++1 version works without any problems. https://godbolt.org/z/PEfeaPvn1

#include <iostream>
#include <cstdint>

int main() {
    std::uint8_t x = 1;
    std::uint8_t y = 2;
    std::uint8_t z = x + y;

    std::cout << z << std::endl;
}

So even if it is not a bug, it is a strong inconvenience. Maybe cpp2::move needs to be extended to prevent this.

MaxSagebaum avatar Oct 14 '24 06:10 MaxSagebaum

It's because the lowered Cpp1 uses list-initialization, which prevents narrowing.

JohelEGP avatar Oct 14 '24 12:10 JohelEGP

Ok, I had a closer look. List-initialization for z still compiles https://godbolt.org/z/YY6fhbbe7. But gives the warning. If we remove the move in the cpp2 example we get the ``same'' warning, which is an error in the compiler settings chosen: https://cpp2.godbolt.org/z/do8Ws4ePx

main: () = {
    x: u8 = 1;
    y: u8 = 2;  
    z: u8 = x + y;
    std::cout << x << " + " << y << " = " << z << std::endl;
}

Error:

main.cpp2:4:19: error: narrowing conversion of '(((int)x) + ((int)y))' from 'int' to 'cpp2::u8' {aka 'unsigned char'} [-Wnarrowing]

So it is the integral promotion you suggested. This seems to be one of those corner cases where the language behaves quite weird. If this would be a generalized rule then also

int main() {
    int x = 1;
    int y = 2;
    int z = x + y;

    std::cout << z << std::endl;
}

should give the same warning.

MaxSagebaum avatar Oct 14 '24 13:10 MaxSagebaum

This makes me wonder if "move from last use" should ignore fundamental types, just to avoid the unnecessary complexity in the generated code.

gregmarr avatar Oct 14 '24 14:10 gregmarr

I also think cpp2::move should also go. Now that compilers are moving towards treating std::move like a built-in for compilation speed, cpp2::move is a step backwards from that.

JohelEGP avatar Oct 14 '24 14:10 JohelEGP

How would you constrain it in that case though? cpp2::move does certain things differently, take a look at #1030.

DyXel avatar Oct 14 '24 16:10 DyXel

cpp2::move was added here https://github.com/hsutter/cppfront/commit/8f4e8557764d7fc0d70c3105ab29c4fa09b78d36#diff-9e3e6d623803ce8a26a44e27a5824cf938eae42d04829e2a41b80bd40a43a246 while fixing #968 and #999. It originally did both move and forward. The forward was intentionally removed here https://github.com/hsutter/cppfront/commit/de3f54f6108d3438d27a76269e1824a771f5b095 so there may not be any reason for this to still exist.

gregmarr avatar Oct 14 '24 16:10 gregmarr

@DyXel It doesn't really do things differently any more. @hsutter couldn't remember why it did things differently, so maybe the need was removed in another way, or maybe it's just been broken again.

It's now just this:

template <typename T>
constexpr auto move(T&& t) -> decltype(auto) {
    return std::move(t);
}

gregmarr avatar Oct 14 '24 16:10 gregmarr

Huh, that's interesting. I thought the original problem was that some objects that were supposed to not be moved were being moved. Like the original example in #1002 . Its quite inconvenient if the common pattern of locking a mutex would require you to discard the mutex later.

DyXel avatar Oct 14 '24 17:10 DyXel

@JohelEGP Thanks, I didn't scroll to the bottom. :)

gregmarr avatar Oct 14 '24 19:10 gregmarr

This seems more like an issue of C++1 integral promotion from uchar to int, I don't think there is anything to fix. @MatthieuHernandez you could work around the issue by declaring x and y as const or using explicit narrowing conversion:

main: () = {
    x: u8 = 1;
    y: u8 = 2;  
    z: u8 = unchecked_narrow<u8>(x + y);
    std::cout << z << std::endl;
}

@hsutter maybe this can be closed?

mihaiboros avatar Feb 15 '25 21:02 mihaiboros