Search code examples
c++ccastingvolatile

Cast to volatile, difference between C and C++


In C, code below compiles fine.

typedef struct T {
    int val;
} T;

void test(volatile T*p, T a) {
    *p = (volatile T)a;  // doesn't work for G++
    *p = a;  // doesn't work for G++
}

But in C++ (tested by gcc 12.2), it is illegal:

<source>: In function 'void test(volatile T*, T)':
<source>:29:22: error: ambiguous overload for 'operator=' (operand types are 'volatile T' and 'volatile T')
   29 |     *p = (volatile T)a;  // doesn't work for G++
      |                      ^
<source>:24:16: note: candidate: 'constexpr T& T::operator=(T&&)' (near match)
   24 | typedef struct T {
      |                ^
<source>:24:16: note:   conversion of argument 1 would be ill-formed:
<source>:29:10: error: binding reference of type 'T&&' to 'volatile T' discards qualifiers
   29 |     *p = (volatile T)a;  // doesn't work for G++
      |          ^~~~~~~~~~~~~
<source>:24:16: note: candidate: 'constexpr T& T::operator=(const T&)' (near match)
   24 | typedef struct T {
      |                ^
<source>:24:16: note:   conversion of argument 1 would be ill-formed:
<source>:29:10: error: binding reference of type 'const T&' to 'volatile T' discards qualifiers
   29 |     *p = (volatile T)a;  // doesn't work for G++
      |          ^~~~~~~~~~~~~
<source>:30:10: error: passing 'volatile T' as 'this' argument discards qualifiers [-fpermissive]
   30 |     *p = a;  // doesn't work for G++
      |          ^
<source>:24:16: note:   in call to 'constexpr T& T::operator=(const T&)'
   24 | typedef struct T {
      |      

Why C++ standard wants to consider it as illegal, and what's the best practice to define the sturct to make it work?

Any suggestions would be appreciated :)

[Edit] I follow the suggestion from @Some programmer dude

For it to be possible the function need to be declared with the volatile qualifier itself: T& operator=(T const&) volatile;

typedef struct T {
    int val;

    T& operator=(const T &a) volatile {
        val = a.val;
        return const_cast<T&>(*this);
    }
} T;

void test(volatile T*p, T a) {
    *p = (volatile T)a;  // doesn't work for G++
    *p = a;  // doesn't work for G++
}

Error message by Clang 15 is

<source>:15:8: error: no viable overloaded '='
    *p = (volatile T)a;  // doesn't work for G++
    ~~ ^ ~~~~~~~~~~~~~
<source>:8:8: note: candidate function not viable: 1st argument ('volatile T') would lose volatile qualifier
    T& operator=(const T &a) volatile {
       ^

Not sure if misunderstood something.


Solution

  • Building with Clang gives a better error message, where it says

    candidate function (the implicit copy assignment operator) not viable: 'this' argument has type 'volatile T', but method is not marked volatile

    What happens is that the C++ compiler creates an implicit assignment operator, it will be declared as

    T& operator=(T const&);
    

    Because the object you assign to is qualified with volatile the compiler-generated assignment operator can't be used. For it to be possible the function need to be declared with the volatile qualifier itself:

    T& operator=(T const&) volatile;
    

    This is similar to const qualified objects and functions. Member functions needs the const qualifier to be called on const objects.


    Now as for how to solve this conundrum: First you have to decide if you really need volatile. Chances are that you don't. Then just remove its use, and the problem will be automatically solved.

    Then you also have to think about the build process itself. Is the code supposed to be C Code, or C++ code? It can't really be both at the same time. While the header-file could be included for both languages, the source code itself can only be one or the other.