Within template class Bank<T>
, my goal is to create an overload for type Account
to member function void Bank<T>::makeTransfer(T, T, const double)
, of signature void Bank<Account>::makeTransfer(Account&, Account&, const double)
.
That is, makeTransfer
function should accept reference parameters when invoked with Account
s, while it should be copying the arguments in for any other type.
What I went with, is creating another makeTransfer<T>
function of similar signature, but accepting reference T&
arguments instead of T
. Then, I added a template specialization to this new function using Account
as T
.
The MWE below however does not compile successfully, see GDBonline compiler link.
#include <cstdio>
#include <functional>
#include <concepts>
class Account {
public:
Account() = default;
Account(const long id, const double balance): _id{id}, _balance{balance} {
printf("Account no.%ld start balance is %f\n", _id, _balance);
}
const long getId() {
return _id;
}
const double getBalance() {
return _balance;
}
void addToBalance(const double sum) {
_balance += sum;
}
private:
long _id;
double _balance;
};
template<typename T> class Bank {
public:
void makeTransfer(T from, T to, const double amount) {
printf("ID %ld -> ID %ld: %f\n", from, to, amount);
}
void makeTransfer(T& from, T& to, const double amount) {}
};
template<> void Bank<Account>::makeTransfer(Account& from, Account& to, const double amount) {
printf("ID %ld -> ID %ld: %f\n", from.getId(), to.getId(), amount);
from.addToBalance(-amount);
to.addToBalance(amount);
printf("Account no.%ld balance is now %f\n", from.getId(), from.getBalance());
printf("Account no.%ld balance is now %f\n", to.getId(), to.getBalance());
}
int main() {
// try with fundamental type as T
Bank<long> bank;
bank.makeTransfer(1000L, 2000L, 49.95);
bank.makeTransfer(2000L, 4000L, 20.00);
// now define a bank with Account instances
Account a{500, 2600};
Account b{1000, 10'000};
Bank<Account> bank2;
bank2.makeTransfer(a, b, 49.95);
bank2.makeTransfer(b, a, 20.00);
//bank2.makeTransfer(std::ref(a), std::ref(b), 49.95);
//bank2.makeTransfer(std::ref(b), std::ref(a), 20.00);
}
Compiler protests of not being able to pick the correct template instantiation:
main.cpp:58:21: error: call of overloaded ‘makeTransfer(Account&, Account&, double)’ is ambiguous
58 | bank2.makeTransfer(a, b, 49.95);
| ~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
main.cpp:31:8: note: candidate: ‘void Bank::makeTransfer(T, T, double) [with T = Account]’
31 | void makeTransfer(T from, T to, const double amount) {
| ^~~~~~~~~~~~
main.cpp:38:17: note: candidate: ‘void Bank::makeTransfer(T&, T&, double) [with T = Account]’
38 | template<> void Bank<Account>::makeTransfer(Account& from, Account& to, const double amount) {
| ^~~~~~~~~~~~~
I tried to inject references explicitly with std::ref
, but that did not help.
Using something like std::same_as<T, Account>
seems redundant, as all cues for the compiler should already be available in the template specialization.
Account(const Account&) = delete;
Account& operator=(const Account&) = delete;
#include <type_traits>
template <typename T>
class Bank {
public:
void makeTransfer(T from, T to, const double amount)
requires(std::is_copy_constructible_v<T> || std::is_copy_assignable_v<T>)
{
printf("ID %ld -> ID %ld: %f\n", from, to, amount);
}
void makeTransfer(T& from, T& to, const double amount) {}
};