Union containing volatile structs
Asked Answered
E

2

6

This seems to be similar to POD structs containing constant member, but sort of reversed.

#include <iostream>

struct A
{
    int a;
};

union U
{
    volatile A a;
    long b;
};

int main()
{
    U u1;
    U u2;

    u1.a.a = 12;
    u2 = u1;
    std::cout << u2.a.a << std::endl;

    return 0;
}

g++ 4.8.3 compiles this code without error and it runs correctly:

$ g++ -std=c++03 a.cpp -o a_gcc
$ ./a_gcc
12

But clang++ 3.5.1 produces an error (I have manually wrapped the error message to keep the code box from scrolling):

$ clang++ -std=c++03 a.cpp -o a_clang
a.cpp:8:7: error: member function 'operator=' not viable: 'this'
argument has type 'volatile A', but function is not marked volatile
union U
      ^
a.cpp:3:8: note: 'operator=' declared here
struct A
       ^
a.cpp:20:5: note: implicit copy assignment operator for 'U' first
required here
        u2 = u1;
        ^
1 error generated.

Does C++03 allow the program to copy-assign a union containing volatile structs? I could not find anything in the C++03 standard that defines the default copy constructor of a union.

I would like to know which compiler is correct or if the standard isn't clear on that point.

Edit: I found out that if I use copy construction instead of copy assignment that both clang++ and g++ will compile the program without error. Specifically if I change main to be:

int main()
{
    U u1;

    u1.a.a = 12;
    U u2 = u1;
    std::cout << u2.a.a << std::endl;

    return 0;
}

.. then it will work. I wonder why they are treated differently by clang++.

Enneahedron answered 5/2, 2015 at 1:53 Comment(3)
What if you add a user-defined assignment operator in union U: volatile U& operator =(const volatile U&) volatile? Or perhaps with only some of those volatiles?Alenaalene
Why not make instances of the union volatile instead of having the union's members be volatile? It seems easier to reason about its behavior that way.Unformed
@JohnZwinck Because then it won't cause this problem.Enneahedron
G
0

In C++11, the union's copy constructor can be deleted. We see this from a note in [class.union], §9.5 in N4140:

[Note: If any non-static data member of a union has a non-trivial default constructor (12.1), copy constructor (12.8), move constructor (12.8), copy assignment operator (12.8), move assignment operator (12.8), or destructor (12.4), the corresponding member function of the union must be user-provided or it will be implicitly deleted (8.4.3) for the union. —end note ]

And in [class.copy], §12.8/25, we see that our union has a non-trivial copy constructor:

A copy/move assignment operator for class X is trivial if it is not user-provided, its parameter-type-list is equivalent to the parameter-type-list of an implicit declaration, and if ...
— [ .. ]
— class X has no non-static data members of volatile-qualified type, and

But that particular line in [class.copy] was only added a result of Is a volatile-qualified type really a POD? Before then, such a class would still be considered to have a trivial copy constructor.

So it's my understanding that in C++03, there is no indication that the union's copy constructor should be deleted, and in C++11, there is some indication of this but it is non-normative.

Ganoid answered 5/2, 2015 at 4:12 Comment(6)
Note that the rule you quoted does not actually apply, since it is the union itself whose copy-assignment operator is non-trivial, but the rule talks about the operators for members... and the copy assignment operator for class A is trivial.Ld
@BenVoigt Hm. Then I got nothing.Ganoid
I think this is still valuable, but definitely non-normative.Ld
@Ganoid Interesting reference to C++11. If I compile my test program in C++11 mode both g++ and clang++ will error that the copy assignment operator of U is implicitly deleted.Enneahedron
@Ganoid For C++11 I think the relevant quote is "a non-static data member of class type M (or array thereof) that cannot be copied/moved because overload resolution (13.3), as applied to M’s corresponding assignment operator, results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operator, or ..." from 12.8/24. The default copy assignment operator of the member can't be used because the default copy assignment operator can't handle volatile (see 12.8/19 and it's footnote). But that is only for C++11, and I haven't been able to find anything for C++03Enneahedron
Sorry to down vote, but this does not answer the question (because it is about C++03), and I would like to keep this in the unanswered questions list until I get an answer. I will undownvote your answer if/when I get an answer which answers the original question.Enneahedron
C
0

This is nothing more than a bug in that particular version of GCC.

C++03 [class.copy]/10 governs the signature of the implicitly declared copy assignment operator for a class. For a class named X, it is either X& operator=(const X&) (most common) or X& operator=(X&) (less common). In the case of A, the copy assignment operator's signature is A& operator=(const A&). Note that this means that you cannot call this assignment operator with a volatile or const volatile value of type A.

Similarly, the implicitly declared assignment operator for U will have the signature U& operator=(const U&). [class.copy]/13 explains how this implicitly declared assignment operator works:

The implicitly-defined copy assignment operator for class X performs memberwise assignment of its subobjects. The direct base classes of X are assigned first, in the order of their declaration in the base-specifier- list, and then the immediate nonstatic data members of X are assigned, in the order in which they were declared in the class definition. Each subobject is assigned in the manner appropriate to its type:

  • if the subobject is of class type, the copy assignment operator for the class is used (as if by explicit qualification; that is, ignoring any possible virtual overriding functions in more derived classes);
  • if the subobject is an array, each element is assigned, in the manner appropriate to the element type;
  • if the subobject is of scalar type, the built-in assignment operator is used.

It is unspecified whether subobjects representing virtual base classes are assigned more than once by the implicitly-defined copy assignment operator. [...]

Thus, U::operator= behaves as though it were implemented like so:

U& operator=(const U& other) {
    a = other.a;
    b = other.b;
    return *this;
}

And a = other.a is ill-formed, because other.a has type volatile A, and A doesn't have an assignment operator that can accept volatile A.

That particular version of GCC probably accepted it because it converted the assignment into a trivial copy operation (i.e. as if by memcpy). It is allowed to do that, but only if the semantic requirements for the assignment are met in the first place. It is not allowed to fail to diagnose a diagnosable error because it decided to perform a particular optimization.

[over.match.oper]/1 makes it clear that if at least one operand for an operator has class type, then overload resolution must be done. In an assignment expression of the form

a = other.a

both operands have class type. [over.match.oper]/3 then explains that the candidates consist of the member candidates, non-member candidates, and built-in candidates. Non-member operator= is permitted, so the second set is empty. The member candidate is A::operator=(const A&);. The list of possible built-in candidates is given in [over.built], but there is no built-in candidate whose left argument is a reference to class type, and [over.match.oper]/4 forbids attempting to perform a user-defined conversion on the left argument in order to convert it to something that a built-in operator= candidate can accept. So the overload resolution rules preclude any possibility that assigning to an A object can be interpreted as a use of a built-in operator. It always must call an assignment operator function, and as I explained previously, the assignment operator for A cannot accept a volatile argument. The compiler must diagnose this.

Clypeate answered 1/5, 2023 at 23:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.