How to let a variable be dependent on other variables inside a class?

What is wrong with the variable international_standard_book_number? How can I make it that it changes, whenever isbn_field_i changes?

Generally speaking: you have to reassign it every time one component changes.

In your particular case: change the constructor using initialization list.

I mean... instead

ISBN(unsigned int a, unsigned int b, unsigned int c, char d)
 {isbn_field_1=a, isbn_field_2=b, isbn_field_3=c, digit_or_letter=d;};

write

ISBN(unsigned int a, unsigned int b, unsigned int c, char d)
 : isbn_field_1{a}, isbn_field_2{b}, isbn_field_3{c}, digit_or_letter{d}
{}

Now your example code write

1-2-3-b

What changes ?

With

ISBN(unsigned int a, unsigned int b, unsigned int c, char d)
 {isbn_field_1=a, isbn_field_2=b, isbn_field_3=c, digit_or_letter=d;};

first your fields are default initialized, so

isbn_field_1    = 0;
isbn_field_2    = 0;
isbn_field_3    = 0;
digit_or_letter = 'a';

international_standard_book_number="0"+"-"+"0"+"-"+"0"+"-"+'a';

then is executed the body of the constructor

isbn_field_1    = 1;
isbn_field_2    = 2;
isbn_field_3    = 3;
digit_or_letter = 'b';

but international_standard_book_number remain unchanged.

With

ISBN(unsigned int a, unsigned int b, unsigned int c, char d)
 : isbn_field_1{a}, isbn_field_2{b}, isbn_field_3{c}, digit_or_letter{d}
{}

the initialization list initialize the fields (and substitute the default initialization)

isbn_field_1    = 1;
isbn_field_2    = 2;
isbn_field_3    = 3;
digit_or_letter = 'b';

and then is executed the default initialization of international_standard_book_number but using the new values, so

international_standard_book_number="1"+"-"+"2"+"-"+"3"+"-"+'b';

Use a member function.

#include <iostream>
#include <string>

class ISBN
{
private:
    unsigned int isbn_field_1=0;
    unsigned int isbn_field_2=0;
    unsigned int isbn_field_3=0;
    char digit_or_letter='a';
    std::string international_standard_book_number() const {
        return std::to_string(isbn_field_1)+"-"+std::to_string(isbn_field_2)+"-"+std::to_string(isbn_field_3)+"-"+digit_or_letter;
    }
public:
    ISBN(){isbn_field_1=0, isbn_field_2=0, isbn_field_3=0, digit_or_letter='a';}
    ISBN(unsigned int a, unsigned int b, unsigned int c, char d){isbn_field_1=a, isbn_field_2=b, isbn_field_3=c, digit_or_letter=d;};
    friend std::ostream &operator<<(std::ostream &os, ISBN const &i) 
    { 
        return os << i.international_standard_book_number();
    }
};


int main()
{
    ISBN test(1,2,3,'b');
    std::cout << test << "\n";
    return 0;
}

Variables in c++ use value sematics. When you do

std::string international_standard_book_number=
std::to_string(isbn_field_1)+"-"+std::to_string(isbn_field_2)+"-"+std::to_string(isbn_field_3)+"-"+digit_or_letter;

it will assign a value to international_standard_book_number based on the values that isbn_field_n has right now. It does not create some kind of automatic link between these variables that make sure they stay in sync.

If you want that behaviour you would have to make sure you update international_standard_book_number everytime one the the other fields are updated.


If you only need to set the value once (e.g. the other values don't change after the object got constructed) you can use an initializer list:

ISBN(unsigned int a, unsigned int b, unsigned int c, char d) 
    : isbn_field_1(a), 
      isbn_field_2(b),
      isbn_field_3(c),
      digit_or_letter(d),
      international_standard_book_number(
          std::to_string(isbn_field_1) + "-" + 
          std::to_string(isbn_field_2) + "-" + 
          std::to_string(isbn_field_3) + "-" + 
          digit_or_letter)
{};

But keep in mind, that the member are still initialized in the order they are declared, not in the order of the initializer list.

Technically, you don't need to initialize international_standard_book_number in the initializer list, as max66's answer shows, it's a question of personal preference.