Giogre
Giogre

Reputation: 1504

C++: Template specialization of class member function, differing from another member function only in accepting parameters by reference, not by value

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 Accounts, 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.

Upvotes: 2

Views: 58

Answers (1)

3CxEZiVlQ
3CxEZiVlQ

Reputation: 38332

  1. From the point of view of any bank, the Account cannot be copied, right? So, it must prevent copies:
      Account(const Account&) = delete;
      Account& operator=(const Account&) = delete;
    
  2. Update the Bank template
    #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) {}
    };
    

https://godbolt.org/z/9sEaov3K8

Upvotes: 3

Related Questions