#Type cast on std::uint8_t different behavior?

54 messages · Page 1 of 1 (latest)

lime perch
#

I have this 'Person' class:

class Person {
  public:
    Person(Age age) : m_age(age) {}

    virtual ~Person() noexcept {}

    virtual double ComputeEarnings() = 0;

    static MaxInstances GetNumberOfInstances() {
      return number_of_instances;
    }

    // Possible causes for the problem:
    // 1. 'std::ostream' doesn't distinguish between 'unsigned char' and 'std::uint8_t'
    // (which is still a 'char' type on many compilers). Explicit casting to 'int'
    // forces 'ostream' to interpret it as a numeric value.
    // 
    // 2. 'ostream' may still interpret 'std::uint8_t' as a character when streamed. This
    // happens because 'ostream' prioritizes overloads for 'char' types over numeric ones.
    //
    // Possible solutions that don't work:
    // 1. Double static casting to unsigned int and then int OR unsigned int and then unsigned int.
    // 2. Unary '+'.
    // 3. Set to a variable with implicit or explicit type cast (neither work).
    friend std::ostream& operator<<(std::ostream& os, const Person& person) {
      int numeric_age = static_cast<int>(person.m_age);
      return os << numeric_age;
    }

  private:
    // Shared across all instances of a class, but with private access (through the getter).
    static MaxInstances number_of_instances;

  protected:
    Age m_age;
};

and I've tried everything, but it always prints the ASCII of the first character (e.g. age = 20 -> it will print 50). I tried it on another program:

#include <iostream>
#include <cstdint>

using Age = std::uint8_t;

class Person {
public:
  explicit Person(Age age) : m_age(age) {}

  friend std::ostream& operator<<(std::ostream& os, const Person& person) {
    os << +person.m_age;
    return os;
  }

private:
  Age m_age;
};

int main() {
  Person p{23};
  std::cout << "Age: " << p << std::endl;  // Outputs: Age: 23
  return 0;
}

and here it works just the way I want it to. Why does this happens? Any help is appreciated :)

wet zenithBOT
#

@lime perch

It looks like you may have code formatting errors in your message

Note: Make sure to use back-ticks (`) and not quotes (')

Markup

```cpp
int main() {}
```

Result
int main() {}
#

When your question is answered use !solved to mark the question as resolved.

Remember to ask specific questions, provide necessary details, and reduce your question to its simplest form. For tips on how to ask a good question use !howto ask.

grave tiger
#

Yes, uint8_t is just an alias and everywhere I know it is an alias for unsigned char, which the stream operators interpret as character. So casting to a different integer type is the solution, as you are doing in both code snippets. What exactly doesn't work the way you want with it?

#

If you are asking why +person.m_age has the same effect as casting to int, that's because operands to arithmetic operators undergo integer promotion before the arithmetic operand is evaluated. Integer promotion converts every integer type which has rank smaller than int to int (if int is able to represent all values of the source type, otherwise unsigned int).

lime perch
#

**and I've tried everything, but it always prints the ASCII of the first character (e.g. age = 20 -> it will print 50)

#

literally none of all these solutions work. not the unary, static cast with different variable or direct static cast, nothing

#

i read on stackoverflow that i can't assign to it from stdin, but i can print it.
i actually thought of something, let me show you

grave tiger
#

They work just fine.

#

!compile

lime perch
#

the sc is an answer that it actually doesn't work for some reason

grave tiger
#

The problem is not with the code you are showing then. It works just as expected, e.g. https://godbolt.org/z/Psh9jfj7c

#

Maybe your problem is reading in the value for the age? The same issue as with the output stream operator exists there as well.

lime perch
#

YES

#

fixed it :D ty ty

#

btw can i ask you sth in case you know?

grave tiger
#

sure

lime perch
#

i like optimizing my code for least resource usage, so i'm trying to also use the minimum required data types as you can see. do you approve of the thing i'm doing? (you might have a lot more experience than i can imagine) and if it's good, i've seen something like this: 'std::uint16_t age : 8;' which would only use 8/16 bits of it

#

** Note: i'm validating everything ofc, before assigning to unsigned (or smaller data type etc).

grave tiger
#

Well, in this case it is certainly premature optimization. It won't ever matter for the kind of program you write in practice. The benefit is also not always obvious. For example you are trying to save on memory here, but in exchange certain operations reading from the field may be less efficient on certain CPUs, etc. Also, because your class is virtual, the memory use will turn out to be the same in the end anyway. There will be extra padding at the end to get it to align to 4 or 8 bytes.

lime perch
grave tiger
#

Usually correctness is more important first: It is possible that someone should be able to input a value that doesn't fit 8 bits? If so, then 8 bits is wrong. If that should be impossible, then choosing a 8bit type is a decent choice. But if you do that you need to verify your input and have a code path if the user inputs something larger. Your current code doesn't check that the input actually fits. Sometimes it is easier to go with the (int) defaults where no extra validation is required.

lime perch
#

for an age of values 1-120, with validation happening (in another program i've made a factory method to check first and then return an object or an error, using std::variant<ClassName object, EnumClass error>)

grave tiger
#

But yes, don't unnecessarily waste memory by using types that you know for sure are larger than necessary, at least if larger than int. There are also typedefs name e.g. uint8_fast_t, which is either 8bit or larger if the larger type has better time performance on the CPU.

#

Whether time or memory efficiency is more important always depends on the use case.

lime perch
#

hmm

lime perch
#

and about the 'uint8_fast_t' thanks

grave tiger
#

But sometimes saving memory also means saving time, because the program is limited by the RAM bandwidth.

lime perch
grave tiger
#

What I want to say is just that it is usually not obvious what gives the "better" result.

lime perch
grave tiger
#

The factory pattern is usually used if you have a polymorphic base class and you want it to return a (unique) pointer to that with different concrete types depending on a runtime value. If that is what you are doing, sure. I can't see it from your code.

lime perch
#

i could send you my 300 lines of code to check it

#

but i would probably tire you

#

tire as in "get you tired"

grave tiger
#

nah, just do what works

lime perch
grave tiger
#

code seldom is perfectly structured and noone agrees what perfect is anyway

#

wait, wait

lime perch
#

this is another program btw

grave tiger
#

write your code whatever way works for you

lime perch
#

the one which has the factory method

grave tiger
#

That's what I meant.

#

I don't really want to review the whole code.

#

But there is also a review channel here.

lime perch
#

aaaaa

lime perch
#

gotchu haha, but still thanks for your time

lime perch
#

so okay, let me close this thread since my initial question was solved. again, thank you for everything!

#

!solved