6

If I had something trivial such as (and to clarify, I am not suggesting this is a good implementation, simply an example to demonstrate partial template specialization failure on member functions):

template <typename T, typename U>
class BankAccount
{
  T money;
  U interestRate;
public:
  BankAccount(T money, U interestRate) :
    money(money), interestRate(interestRate)
  {}

  void showMeTheMoney();
};

I would not be able to specialize each function by doing:

// invalid code
template <typename U>
void BankAccount <int, U>::showMeTheMoney()
{
  printf("$%d.00 interest: %f\n", money, interestRate);
}

template <typename U>
void BankAccount <long, U>::showMeTheMoney()
{
  printf("$%l.00 interest: %f\n", money, interestRate);
}

template <typename U>
void BankAccount <float, U>::showMeTheMoney()
{
  printf("$%.2f interest: %f\n", money, interestRate);
}

template <typename U>
void BankAccount <double, U>::showMeTheMoney()
{
  printf("$%.2f interest: %f\n", money, interestRate);
}

int main(int argc, char *argv[])
{
  BankAccount<double, float> b(500, 0.03);
  b.showMeTheMoney();
  BankAccount<std::uint64_t, float> c(1234, 0.01);
  c.showMeTheMoney();
}

etc. Unfortunately C++ standards do not allow this:

14.5.5.3.1. The template parameter list of a member of a class template partial specialization shall match the template parameter list of the class template partial specialization. The template argument list of a member of a class template partial specialization shall match the template argument list of the class template partial specialization.

So instead the only solutions (that I know of) are to use type traits or reproduce the entire class with boilerplate code. Is there a rationale behind this decision, or is this something that simply does not exist in C++ because there isn't enough demand, or some other reason?

Michael Choi
  • 610
  • 5
  • 22
  • 2
    Unless someone has made a rejected proposal to implement what you are asking about, it's very difficult to objectively answer why that feature doesn't exist. – François Andrieux Dec 19 '18 at 16:40
  • For what it's worth, Rust does things like this, while C++ does not. If you'd like C++ to do this, might want to join a committee. – tadman Dec 19 '18 at 16:40
  • 1
    offtopic: storing amount of money in a bank account as flaoting point is not a good idea – 463035818_is_not_an_ai Dec 19 '18 at 16:41
  • Could you refactor `showMeTheMoney()` into a non-member function which you can then specialize to operate on specific `BankAccount` types? – TypeIA Dec 19 '18 at 16:43
  • @TypeIA That could be possible, but that's actually why I used the example of a bank account. I'd like to assume that the implementation details should be internal and that the 'money' member would normally not be accessible outside. – Michael Choi Dec 19 '18 at 16:49
  • @user463035818 I assumed there was an issue with that. I used the example of a bank account because it was easy and trivial to do while implying that access to the field 'money' should not be publicly available. Out of curiosity, (this is again offtopic) how should money be stored in a bank account? Encrypted with a 2 way encryption? – Michael Choi Dec 19 '18 at 16:51
  • I dont know the details, but for sure it should be a type with exact arithmetics (which floating point types are not) – 463035818_is_not_an_ai Dec 19 '18 at 16:53
  • 2
    @MichaelChoi Use an integer type and store the cents: https://stackoverflow.com/questions/149033/best-way-to-store-currency-values-in-c – NathanOliver Dec 19 '18 at 16:56
  • 1
    @MichaelChoi and it should probably be *stored* as a sequence of `Transaction` objects – Caleth Dec 19 '18 at 17:03
  • @nathanoliver Or likely even tenths or hundredths (thousandths?) of whatever your smallest denomination is. Otherwise doing e.g. interests will be inaccurate. – ravnsgaard Dec 19 '18 at 17:44
  • This is the language telling you these functions shouldn't be member functions. – rubenvb Dec 19 '18 at 18:33
  • @FrançoisAndrieux Because it is sufficiently complex for some compilers (clang) to be confused by the rule. For example if you declare a specialization for a member of a class template, and latter declare a partial specialization of that class template in such a way that the previously declared specialization appears to be a specialization of a member of the partial specialization, then Clang will fail to recognize it (there is an open language issue about that). Clearly if you reached that point, it appears clearly it is sufficiently complex! – Oliv Dec 19 '18 at 22:22

1 Answers1

1

Because it is not a template specialization of a member function that you have written. It is specialization of the class. Hence, the code should look like this (I added a common base class that you don not have to define the members for all specifications):

template <typename T, typename U>
class BankAccountBase
{
protected:
    T money;
    U interestRate;

public:
    BankAccountBase(T money, U interestRate) :
        money(money), interestRate(interestRate)
    {}
};

template <typename T, typename U>
class BankAccount
{
    T money;
    U interestRate;
public:
    BankAccount(T money, U interestRate) :
        money(money), interestRate(interestRate)
    {}

    void showMeTheMoney();
};

template <typename U>
class BankAccount <int, U> : public BankAccountBase <int, U>
{
public:
    BankAccount(int money, U interestRate) :BankAccountBase(money, interestRate) { }
    void showMeTheMoney();
};

template <typename U>
class BankAccount <long, U> : public BankAccountBase <long, U>
{
public:
    BankAccount(long money, U interestRate) :BankAccountBase(money, interestRate) { }
    void showMeTheMoney();
};
template <typename U>
class BankAccount <float, U> : public BankAccountBase <float, U>
{
    BankAccount(float money, U interestRate) :BankAccountBase(money, interestRate) { }
public:
    void showMeTheMoney();
};
template <typename U>
class BankAccount <double, U> : public BankAccountBase <double, U>
{
public:
    BankAccount(double money, U interestRate) :BankAccountBase(money, interestRate) { }
    void showMeTheMoney();
};

template <typename U>
class BankAccount <long long, U> : public BankAccountBase <long long, U>
{
public:
    BankAccount(long long money, U interestRate) :BankAccountBase(money, interestRate) { }
    void showMeTheMoney();
};


// invalid code
template <typename U>
void BankAccount <int, U>::showMeTheMoney()
{
    printf("$%d.00 interest: %f\n", money, interestRate);
}

template <typename U>
void BankAccount <long, U>::showMeTheMoney()
{
    printf("$%l.00 interest: %f\n", money, interestRate);
}

template <typename U>
void BankAccount <long long, U>::showMeTheMoney()
{
    printf("$%l.00 interest: %f\n", money, interestRate);
}

template <typename U>
void BankAccount <float, U>::showMeTheMoney()
{
    printf("$%.2f interest: %f\n", money, interestRate);
}

template <typename U>
void BankAccount <double, U>::showMeTheMoney()
{
    printf("$%.2f interest: %f\n", money, interestRate);
}

int main(int argc, char *argv[])
{
    BankAccount<double, float> b(500, 0.03);
    b.showMeTheMoney();
    BankAccount<long long, float> c(1234, 0.01);
    c.showMeTheMoney();
}
Jürgen
  • 76
  • 3