Search code examples
c++structvolatile

How can I make a volatile struct behave exactly like a volatile int during assignment?


When one assigns to a volatile int from a non-volatile int, the compiler. When one assigns from a volatile struct from a non-volatile struct of the same type, the compiler appears to be extremely unhappy.

Consider the following simple program.

struct Bar {
    int a;
};

volatile int foo;
int foo2;

volatile Bar bar;
Bar bar2;

int main(){
    foo = foo2;
    bar = bar2;
}

When I try to compile this code, I get an error on the second line of main but not the first.

g++     Main.cc   -o Main
Main.cc: In function ‘int main()’:
Main.cc:13:9: error: passing ‘volatile Bar’ as ‘this’ argument discards qualifiers [-fpermissive]
     bar = bar2;
         ^
Main.cc:1:8: note:   in call to ‘Bar& Bar::operator=(const Bar&)’
 struct Bar {

It seems that the problem occurs because a volatile Bar is being passed to the left side of the assignment operator, although I am not sure why this is not a problem for int.

I looked at this answer which suggested the following fix.

struct Bar {
    int a;
    volatile Bar& operator= (const Bar& other) volatile {
       *this = other; 
    }
};

Unfortunately, this resulted in the following two warnings.

g++     Main.cc   -o Main
Main.cc: In member function ‘volatile Bar& Bar::operator=(const Bar&) volatile’:
Main.cc:4:21: warning: implicit dereference will not access object of type ‘volatile Bar’ in statement
        *this = other; 
                     ^
Main.cc: In function ‘int main()’:
Main.cc:16:15: warning: implicit dereference will not access object of type ‘volatile Bar’ in statement
     bar = bar2;

I then looked at this answer, which mentions that I should cast the reference to an rvalue, but I am not sure which reference to cast, and which cast syntax to use in this case.

What is the correct incantation to make the assignment on the line 2 of main behave exactly as line 1 of main does, without warnings or errors?


Solution

  • Your initial problem is because the implicit assignment operator has signature

    Bar& operator=(const Bar& rhs);
    

    ... and that isn't callable for a volatile object. The warnings are because your updated function returns a volatile reference, but that reference is never used. GCC thinks that this might be a problem. The simplest way to fix this is to change the return type to void!

    There is another problem: Your function will call itself in an infinite recursion. I suggest the following:

    struct Bar {
        int a;
        Bar& operator=(const Bar&rhs) = default;
        void operator=(const volatile Bar& rhs) volatile // Note void return.
        {
             // Caution: This const_cast removes the volatile from
             // the reference.  This may lose the point of the original
             // volatile qualification.
             //
             // If this is a problem, use "a = rhs.a;" instead - but this
             // obviously doesn't generalize so well.
             const_cast<Bar&>(*this) = const_cast<const Bar&>(rhs);
        }
    };
    
    volatile Bar vbar;
    Bar bar;
    
    int main(){
        vbar = bar;  // All four combinations work.
        bar = vbar;
        vbar = vbar;
        bar = bar;
        return 0;
    }
    

    This means you won't be able to chain assignment operators when using volatile structs. I assert this is not a huge loss.

    Final aside: Why are you using volatile - it's not very useful for multi-threaded code (it is useful for memory mapped IO).