1

I have an object declaration Bitmap image(FileRead(filename)) that is ... really confused?

When I hover over filename in Visual Studio, it gives me the tooltip FileRead filename as if I were declaring a new variable in the parameter list. When I hover over image it gives me Bitmap image(FileRead filename) as if it were a function declaration. These are my only clues as the compiler only gives me a compilation error if I attempt to use one of image's methods, confirming that image at least isn't being interpreted as an object.

If I change the argument so it's Bitmap image(FileRead("strLiteral")) or if I add a second optional argument so it's Bitmap image(FileRead(filename), 4), the problem goes away. Maybe one of the other constructors is confusing it but... I'm fairly sure that type of ambiguity is it's own class of compilation error, what I'm getting is totally different?

I'm genuinely mystified, what could be going on here?

int main(int argc, char * argv[]){

    const char * filename = "image.png";

    Bitmap image(FileRead(filename));

    // using either of these instead works as intended for some reason
    Bitmap image(FileRead(filename), 4u);   
    Bitmap image(FileRead("image.png")); 

    // image.get*() generates the following compilation error: 
    // "Error   C2228   left of '.getY' must have class/struct/union"
    // "Error   C2228   left of '.getX' must have class/struct/union"

    for (uint32 y = 0; y < image.getY(); ++y) {  
        for (uint32 x = 0; x < image.getX(); ++x) {

        }
    }
    return 0;
}

Here's all of the constructor signatures to FileRead and Bitmap, in case something in here was causing trouble.

class Bitmap {
    friend Mipmaps;
public:
    Bitmap();
    Bitmap(const Bitmap & other, bool flipY = false);
    Bitmap(Bitmap && other, bool flipY = false);
    Bitmap & operator=(const Bitmap & other);
    Bitmap & operator=(Bitmap && other);
    Bitmap(FileRead & file, uint16 byteDepth = 4u, uint16 heightForVolume = 0, bool flipY = false); 
    Bitmap(const uint8 * compressedImgData, uint32 imgDataLen, uint16 byteDepth, uint16 heightForVolume = 0, bool flipY = false);
    Bitmap(uint16 byteDepth, glm::u16vec2 size);
    Bitmap(uint16 byteDepth, glm::u16vec3 size);
    static Bitmap asBorrowed(uint8 * data, glm::u16vec4 size, TextureDim::Type dimensionality = TextureDim::dim2D);
    ...
    uint16 getX() const                 { return m_size.x; }
    uint16 getY() const                 { return m_size.y; }
};
class FileRead{
public:
    FileRead(const char * filename, bool textMode = false, uint32 bufferSize = 4096);
    ...
};
Anne Quinn
  • 12,609
  • 8
  • 54
  • 101
  • 4
    You have encountered [the most vexing parse](https://en.wikipedia.org/wiki/Most_vexing_parse). With `Bitmap image(FileRead(filename));` you declare `image` as a ***function***, taking a `FileRead` object as argument, and returning a `Bitmap` object. Use curly-braces instead: `Bitmap image{FileRead(filename)};` – Some programmer dude Aug 20 '21 at 06:05
  • 1
    @Someprogrammerdude - oh my god... "vexing" is a very PG way of putting it, thank you – Anne Quinn Aug 20 '21 at 06:08
  • @AnneQuinn, essentially you thought `image` was a `Bitmap`, but it is not, which you can verify with `static_assert(std::is_same_v, "");` which fails; on the other hand `static_assert(std::is_same_v, "");` passes, meaning that `image` is a `Bitmap(Fileread)`. – Enlico Aug 20 '21 at 06:12
  • 1
    ALso, a must to watch CppCon 2018: Nicolai Josuttis “The Nightmare of Initialization in C++” https://www.youtube.com/watch?v=7DTlWPgX6zs :P – Swift - Friday Pie Aug 20 '21 at 06:12
  • 1
    You _should_ be using curly braces to construct the unnamed `FileRead` object at the very least, giving you `Bitmap image(FileRead{filename});` but you can leave off the type name! Actually, you didn't declare that constructor `explicit`, so you can leave off the curly braces too: `Bitmap image(filename);`. But, you ought to use curly braces for initialization in general, so you have `Bitmap image { filename };`. – JDługosz Aug 20 '21 at 06:15
  • @AnneQuinn, in general, when you see some strange error that makes you think "why does the compiler think this object lacks properties/methods that I've clearly defined in its class?", then `static_assert` or triggering compiler error via `void xxx{the_misbhaving_object};` is one useful way to go. – Enlico Aug 20 '21 at 06:16

1 Answers1

3

This is a less common case of Most Vexing Parse, which got its name from reaction similar to yours.

The idea is that by application of Murphy law, if something can be translated wrong, it will, so if a declaration of variable looks like function declaration, it is the latter.

FileRead is a type name, in consequence FileRead(filename) can be read as FileRead filename according to syntax of declarations in C and C++, so what you have there a prototype of function named image:

Bitmap image(FileRead filename);
Swift - Friday Pie
  • 12,777
  • 2
  • 19
  • 42