Resolving a compiler error due to an invariant member with a possible deleted default constructor

青春壹個敷衍的年華 提交于 2019-12-11 14:55:47

问题


I have asked a series of questions that all relate to the same source code in this order:

  1. experimenting-with-unions-and-bitfields-within-a-structures-and-templates
  2. trying-to-flip-the-order-of-bits-in-stdbitset
  3. avoiding-ambiguity-in-overload-resolution

I've also asked these series of questions over at Code Review that are also related.

  1. emulating-virtual-registers-by-experimenting-with-unions-bitfields-structs-and-template-specialization
  2. emulating-virtual-registers-part-2

This should give you an overview of my original code design and it is there for reference and background information. Since then I started to look at my working code and wanted to simplify it even more.

I then decided to remove the template specializations and have my Register class be 64 bits wide instead of 8 bits by default while specializing higher order sized registers.

I'm trying to incorporate the idea that one can access the data in any of these manners:

  • Full value: - the entire 64 bit qword
  • Half value(s): - 2 individual 32 bit dwords
  • Quarter value(s) - 4 individual 16 bit words
  • Eighth value(s) - 8 individual 8 bit bytes

And through the utility use of std::bitset one can easily access any of the bits within the full 64 bit register. With the use of unions I should be able to correctly map the memory so that a register can be represented as and accessed by any of these combinations:

  • std::bitset<64> qword
  • std::bitset<32> dword[2]
  • std::bitset<16> word[4]
  • std::bitset<8> byte[8]

The concept of using the union is to have a single space in memory that represents 64 bits for any given register. Now I'm trying to keep my Register class trivially copyable.

So I had modified the original code from what can be found throughout the links above to a simpler version of this:

Register.h

#include <algorithm>
#include <bitset>
#include <string>
#include <vector>

namespace vpc {
    typedef std::int8_t  i8;
    typedef std::int16_t i16;
    typedef std::int32_t i32;
    typedef std::int64_t i64;

    const std::uint16_t BYTE = 0x08;
    const std::uint16_t WORD = 0x10;
    const std::uint16_t DWORD = 0x20;
    const std::uint16_t QWORD = 0x40;

    typedef std::bitset<BYTE>  Byte;
    typedef std::bitset<WORD>  Word;
    typedef std::bitset<DWORD> DWord;
    typedef std::bitset<QWORD> QWord;

    union Bits {
        QWord value;
        DWord dword[2];
        Word  word[4];
        Byte byte[8];
    };

    struct Register {
        Bits bits;
        Register() = default;
    };       

} // namespace vpc

Then I want to test to make sure that everything so far is trivially copyable. So I run this short program.

main.cpp

#include <iostream>
#include <type_traits>
#include "Register.h"

int main() {
    using namespace vpc;

    std::cout << std::boolalpha;

    std::cout << "std::bitset<64> is trivially copyable " 
        << std::is_trivially_copyable<std::bitset<64>>::value << '\n'
              << "QWord is trivially copyable "
        << std::is_trivially_copyable<QWord>::value << '\n'
              << "DWord is trivially copyable "
        << std::is_trivially_copyable<DWord>::value << '\n'
              << "Word is trivially copyable "
        << std::is_trivially_copyable<Word>::value << '\n'
              << "Byte is trivially copyable "
        << std::is_trivially_copyable<Byte>::value << '\n'
              << "Bits is trivially copyable "
        << std::is_trivially_copyable<Bits>::value << '\n'
              << "Register is trivially copyable "
        << std::is_trivially_copyable<Register>::value << '\n';

    return EXIT_SUCCESS;
}

And I get this output:

std::bitset<64> is trivially copyable true
QWord is trivially copyable true
DWord is trivially copyable true
Word is trivially copyable true
Byte is trivially copyable true
Bits is trivially copyable true
My Register is trivially copyable true

Now when looking at the union Bits it states that it is trivially copyable. So instead of declaring a variable of type Bits in the struct as its data member; I believe we should be able to have an anonymous union within our structure so we can get right to our qword, dwords, words and bytes directly. So now the class will look like this:

struct Register {
    union {
        QWord value;
        DWord dword[2];
        Word  word[4];
        Byte  byte[8];
    };

    Register() = default;
};

Then I run this line of code in our main.cpp

// code...

std::cout << std::boolalpha;
std::cout << "Register is trivially copyable "
          << std::is_trivially_copyable<Register>::value << '\n';

// code...

And I get this output:

Register is trivially copyable true

Okay so far so good.

Now I'm working on my function that operates on a Register object. It will reverse the order of the bits as seen from the previously asked questions. Except in this case I'm not using templates. So here I declare the function prototype in Register.h after my class:

Register reverseBitOrder( Register& reg, bool copy = false );

And then I created a Register.cpp file just to implement this function.

Register.cpp

#include "Register.h"

namespace vpc {

    Register reverseBitOrder(Register& reg, bool copy) {
        auto str = reg.value.to_string();
        std::reverse(str.begin(), str.end());

        if (copy) { // return a copy
            Register cpy;
            cpy.value = QWord(str);
            return cpy;
        } else {
            reg.bits.value = QWord(str);
            return { 0 };
        }
    }

} // namespace vpc

So now that I have my function written I clean my solution and now I try to compile "Register.h". However; I'm getting this compiler error frpm Visual Studio 2017 with the language setting set to latest draft standard or flag (/std:c++latest).

--- Build started: Project: Corgi64, Configuration: Debug Win32 ------
1>Register.cpp
1>c:\***\register.cpp(10): error C2280: 'vpc::Register::Register(void)': attempting to reference a deleted function
1>c:\***\register.h(40): note: see declaration of 'vpc::Register::Register'
1>c:\***\register.h(40): note: 'vpc::Register::Register(void)': function was implicitly deleted because 'vpc::Register' has a variant data member 'vpc::Register::value' with a non-trivial default constructor
1>c:\***\register.h(34): note: see declaration of 'vpc::Register::value'
1>Done building project "Corgi64.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

So when I click on error C2280 it directs me to the declaration of my Register variable cpy. When I move my mouse cursor overtop of the variable cpy it gives me the message:

vpc::Register cpy

the default constructor of "vpc::Register" cannot be referenced -- it is a deleted function

So my question becomes: Why is the default constructor being deleted if everything from before was trivially copyable? Since I am now using it within a function, all of a sudden, it is stating that my structure has an invariant member that doesn't have a trivially copyable constructor and it is pointing to Register::value as the culprit. What is causing this, how and why? What can I do to fix or resolve this?


回答1:


Here's a shorter reproduction:

struct T {
    T() { }
};

union X {
    T t;
};

static_assert(std::is_trivially_copyable_v<T>); // fine
static_assert(std::is_trivially_copyable_v<X>); // fine

X x; // error: use of deleted function X::X()

The trivially copyable requirement doesn't actually check the default constructor - it's just about the copy/move constructor/assignment. That's a red herring here. Let's look at the default constructor rules:

A defaulted default constructor for class X is defined as deleted if:

  • X is a union that has a variant member with a non-trivial default constructor and no variant member of X has a default member initializer,
  • [...]

In our X, we have a variant member with a non-trivial default constructor (T() is user provided, hence it's non-trivial... and std::bitset's default constructor actually does something), so the default constructor is defined as deleted. In this example, the default constructor is implicitly defaulted - in the OP it's explicitly defaulted - but the effect is the same.

The workaround is to provide a default constructor that does... whatever it is you want the default constructor to actually do:

union X {
    X() : t() { }
    T t;
};

The rule of thumb here is that union special members are only implicitly provided if all the variants are trivial.




回答2:


After reading what user Barry had stated in his answer and I went back to look at my code I was able to do come up with this:

struct Register {
    Bits bits;
    Register() : value{0}{}
};

for my Register class and I changed my function definition to this:

MyRegister reverseBitOrder(MyRegister& reg, bool copy) {
    auto str = reg.value.to_string();
    std::reverse(str.begin(), str.end());

    if (copy) { // return a copy
        MyRegister cpy;
        cpy.value = QWord(str);
        return cpy;
    } else {
        reg.value = QWord(str);
        return {};
    }
}

And now my code compiles fine and I'm getting my expected outputs.



来源:https://stackoverflow.com/questions/56006786/resolving-a-compiler-error-due-to-an-invariant-member-with-a-possible-deleted-de

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!