简体   繁体   English

需要在类模板中为朋友操作员+自动返回类型推导清晰度

[英]Needing clarity on auto return type deduction for friend operator+ in a class template

I'm just wanting some clarity here with what is happening with this code and why it is behaving as such: 我只是想清楚一下这段代码发生了什么,以及为什么它的表现如下:

main.cpp main.cpp中

#include <fstream>
#include <iostream>
#include <iomanip>
#include <sstream>

#include "Register.h"

int main() {
    using namespace vpc;

    Reg8 r8{ 0xEF };

    Reg16 expected{ 478 };
    Reg16 r16a = r8 + r8;
    Reg16 r16b{ r8 + r8 };
    std::cout << expected << r16a << r16b;

    return EXIT_SUCCESS;
}

The code in my main function doesn't change as it is the same for both cases: 我的main函数中的代码没有改变,因为两种情况都是一样的:


This is my operator+ that I'm working on and this was my first attempt: 这是我的operator+我正在努力,这是我的第一次尝试:

template<typename Lhs, typename Rhs>
auto operator+(const Register<Lhs>& l, const Register<Rhs>& r) {
    auto tmp = l.value + r.value;
    if (sizeof(l.value) < sizeof(r.value))
        return Register<Rhs>{ tmp };
    else
        return Register<Lhs>{ tmp };
}

And this was the program's output: 这是该计划的输出:

Output v1 输出v1

Reg16(478)
Prev: 0
hex: 0x01DE
bin: 0000000111011110

Reg16(222)
Prev: 0
hex: 0x00DE
bin: 0000000011011110

Reg16(222)
Prev: 0
hex: 0x00DE
bin: 0000000011011110

As you can see above the expected value should be 478 in decimal or 0x01DE in hex. 如您所见,预期值应为十进制478或十六进制0x01DE However the operator= and the Register<T> constructors are not getting the appropriate value from the operator+ in this case. 但是,在这种情况下, operator=Register<T>构造函数没有从operator+获得适当的值。


I was able to resolve this issue by changing my operator+ to this: 我能够通过将operator+更改为此来解决此问题:

template<typename Lhs, typename Rhs>
auto operator+(const Register<Lhs>& l, const Register<Rhs>& r) {
    return Register<decltype(l.value + r.value)>{ l.value + r.value };
}

And this is giving me the correct result: 这给了我正确的结果:

Output v2 输出v2

Reg16(478)
Prev: 0
hex: 0x01DE
bin: 0000000111011110

Reg16(478)
Prev: 0
hex: 0x01DE
bin: 0000000111011110

Reg16(478)
Prev: 0
hex: 0x01DE
bin: 0000000111011110

If you need to see my full class implementation you can find it below my question(s) - concern(s): I'm looking for clarity and a better understanding of the behaviors here. 如果您需要查看我的完整课程实现,您可以在我的问题下面找到它 - 关注点:我正在寻找清晰度并更好地理解这里的行为。

What I would like to know is why is the first version not producing the correct or expected value and why the second attempt does. 我想知道的是为什么第一个版本没有产生正确或预期的值以及第二个尝试的原因。 What are the main differences between the two implementations as well as what is going on under the hood with in the compiler? 这两个实现之间的主要区别是什么,以及编译器内部的内容是什么? I'm using Visual Studio 2017. 我正在使用Visual Studio 2017。


Register.h Register.h

#pragma once

#include <algorithm>
#include <bitset>
#include <cassert>
#include <climits>
#include <cstdint>
#include <iterator>
#include <iostream>
#include <iomanip>
#include <limits>
#include <map>
#include <string>
#include <type_traits>

namespace vpc {
    using u8  = std::uint8_t;
    using u16 = std::uint16_t;
    using u32 = std::uint32_t;
    using u64 = std::uint64_t;

    template<typename T>
    struct Register;

    using Reg8  = Register<u8>;
    using Reg16 = Register<u16>;
    using Reg32 = Register<u32>;
    using Reg64 = Register<u64>;

    template<typename T>
    struct Register {
        T value;
        T previous_value;
        std::bitset<sizeof(T)* CHAR_BIT> bits;

        Register() : value{ 0 }, previous_value{ 0 }, bits{ 0 } {}

        template<typename U, std::enable_if_t<(sizeof(U) > sizeof(T))>* = nullptr>
        explicit Register(const U val, const u8 idx = 0) :
            value{ static_cast<T>((val >> std::size(bits) * idx) &
                  std::numeric_limits<std::make_unsigned_t<T>>::max()) },
            previous_value{ 0 },
            bits{ value }
        {
            constexpr u16 sizeT = sizeof(T);
            constexpr u16 sizeU = sizeof(U);
            assert((idx >= 0) && (idx <= ((sizeU / sizeT) - 1)) );
        }

        template<typename U, std::enable_if_t<(sizeof(U) < sizeof(T))>* = nullptr>
        explicit Register(const U val, const u8 idx = 0) :
            value{ static_cast<T>((static_cast<T>(val) << sizeof(U)*CHAR_BIT*idx) &
                  std::numeric_limits<std::make_unsigned_t<T>>::max()) },
            previous_value{ 0 },
            bits{ value }
        {
            constexpr u16 sizeT = sizeof(T);
            constexpr u16 sizeU = sizeof(U);
            assert((idx >= 0) && (idx <= ((sizeT / sizeU) - 1)) );
        }

        template<typename U, std::enable_if_t<(sizeof(U) == sizeof(T))>* = nullptr>
        explicit Register(const U val, const u8 idx = 0) :
            value{ static_cast<T>( val ) }, previous_value{ 0 }, bits{ value }
        {}

        template<typename... Args>
        Register(Args... args) {}

        template<typename U>
        Register(const Register<U>& reg, const u8 idx = 0) : Register(reg.value, idx) {}

        void changeEndian() {
            T tmp = value;
            char* const p = reinterpret_cast<char*>(&tmp);
            for (size_t i = 0; i < sizeof(T) / 2; ++i)
                std::swap(p[i], p[sizeof(T) - i - 1]);
            bits = tmp;
        }

        Register& operator=(const Register& obj) {
            this->value = obj.value;
            this->previous_value = obj.previous_value;
            this->bits = obj.bits;
            return *this;
        }

        template<typename Lhs, typename Rhs>
        friend auto operator+(const Register<Lhs>& l, const Register<Rhs>& r);
    };
} // namespace vpc    

Three points. 三点。

(1) Remember to use if constexpr , instead of simply if , in your first version of operator+ () (1)记得要用if constexpr ,而不是简单的if ,在你的第一个版本的operator+ ()

template<typename Lhs, typename Rhs>
auto operator+(const Register<Lhs>& l, const Register<Rhs>& r) {
    auto tmp = l.value + r.value;
    if constexpr (sizeof(l.value) < sizeof(r.value)) // if constexpr here!
        return Register<Rhs>{ tmp };
    else
        return Register<Lhs>{ tmp };
}

otherwise auto deduction type doesn't works when sizeof(l.value) is different from sizeof(r.value) . 否则当sizeof(l.value)sizeof(r.value)不同时, auto扣除类型不起作用。

(2) From your first version of operator() (that works because you sum two value of the same type) you have an overflow. (2)从你的第一个operator()版本operator()因为你将两个相同类型的值相加而起作用),你会有溢出。

More exactly: 更确切地说:

  • Lhs and Rhs are std::uint8_t so the function return a Register<std::uint8_t> . LhsRhsstd::uint8_t所以函数返回Register<std::uint8_t>
  • tmp become std::uint32_t (see point 3) but assigning it to a std::uint8_t loose the overflow tmp变为std::uint32_t (参见第3点)但是将它分配给std::uint8_t松散溢出

(3) From my platform, from the code (3)从我的平台,从代码

std::cout << sizeof(char) << std::endl;
std::cout << sizeof(std::declval<char>()+std::declval<char>()) << std::endl;
std::cout << sizeof(short) << std::endl;
std::cout << sizeof(std::declval<short>()+std::declval<short>()) << std::endl;

I get 我明白了

1
4
2
4

It's called "integral promotion". 它被称为“整体推广”。

In short: the sum of two char become a int ; 简而言之:两个char的总和成为一个int ; the sum of two short become a int . 两个short的总和成为一个int

This should clarify why works (but not exactly as you want, I suppose) your second version of operator+ () 这应该澄清为什么有效(但不完全是你想要的,我想)你的第二个版本的operator+ ()

template<typename Lhs, typename Rhs>
auto operator+(const Register<Lhs>& l, const Register<Rhs>& r) {
    return Register<decltype(l.value + r.value)>{ l.value + r.value };
}

You have that decltype(l.value + r.value) is int ; 你有那个decltype(l.value + r.value)int ; so that decltype(R8+R8) is a R32 . 因此, decltype(R8+R8)R32

What are Rhs and Lhs when 什么是RhsLhs

r8 + r8

is called? 叫做? Both are uint8_t , so in first version 两者都是uint8_t ,所以在第一个版本中

auto tmp = l.value + r.value;
if (sizeof(l.value) < sizeof(r.value))
    return Register<Rhs>{ tmp };
else
    return Register<Lhs>{ tmp };

temporary tmp = 478 is passed to Register whose value type is uint8_t , and you lose data. 临时tmp = 478传递给其value类型为uint8_t Register ,并丢失数据。


In second version 在第二个版本

return Register<decltype(l.value + r.value)>{ l.value + r.value };

you are using decltype to get type of l.value + r.value . 您正在使用decltype来获取l.value + r.value类型。 Both types are uint_8 , but while performing integer operation both are promoted to int , so decltype() returns int , width of int is enough to store 478. 这两种类型的uint_8 ,但在执行整数操作二者都提升到int ,如此decltype()返回int ,的宽度int足以存储478。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM