I'm just wanting some clarity here with what is happening with this code and why it is behaving as such:
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:
This is my operator+
that I'm working on and this was my first attempt:
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
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. However the operator=
and the Register<T>
constructors are not getting the appropriate value from the operator+
in this case.
I was able to resolve this issue by changing my operator+
to this:
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
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.
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+ ()
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)
.
(2) From your first version of operator()
(that works because you sum two value of the same type) you have an overflow.
More exactly:
Lhs
and Rhs
are std::uint8_t
so the function return a Register<std::uint8_t>
.tmp
become std::uint32_t
(see point 3) but assigning it to a std::uint8_t
loose the overflow(3) From my platform, from the code
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
; the sum of two short
become a int
.
This should clarify why works (but not exactly as you want, I suppose) your second version of 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
; so that decltype(R8+R8)
is a R32
.