Abaixo, tenho duas hierarquias de classes de modelo separadas ( Account
-> CheckingAccount
e Logger
-> ConsoleLogger
) e uma Bank
classe de modelo que usa ambas.
O objetivo é usar Logger
s diferentes, um para tipos fundamentais como long
, e outro adaptado para Account
objetos, este último recebendo um argumento de ponteiro devido ao polimorfismo de Account
.
Suponho que isso poderia ser alcançado inteiramente com modelos, no entanto, resolver isso deve lançar alguma luz sobre como a especialização de modelos funciona para hierarquias de herança de classes, se é que isso acontece.
O MWE abaixo não compila com sucesso, veja o link do compilador .
#include <cstdio>
//#include <concepts>
//#include <type_traits>
class Account {
public:
virtual ~Account() {}
virtual const long getId() = 0;
};
class CheckingAccount: public Account {
public:
CheckingAccount() = default;
CheckingAccount(const long id): _id{id} {}
~CheckingAccount() {}
const long getId() {
return _id;
}
private:
long _id;
};
////////////////////////////////////////////////////////////////
template<typename T> class Logger {
public:
virtual void logTransfer(T, T, const double) = 0;
};
template<typename T> class ConsoleLogger : public Logger<T> {
public:
void logTransfer(T from, T to, const double amount) override {
printf("[console] %ld -> %ld: %f\n", from, to, amount);
}
};
// template class specialization
template<> class ConsoleLogger<Account*> : public Logger<Account*> {
public:
void logTransfer(Account* from, Account* to, const double amount) override {
printf("[console] %ld -> %ld: %f\n", from->getId(), to->getId(), amount);
}
};
////////////////////////////////////////////////////////////////////
template<typename T> struct Bank {
void setLogger(Logger<T>* new_logger) {
logger = new_logger;
}
void logTransfer(T from, T to, const double amount) {
if(logger)
logger->logTransfer(from, to, amount);
}
private:
Logger<T>* logger;
};
// template class specialization
template<> struct Bank<Account*> {
void setLogger(Logger<Account*>* new_logger) {
logger = new_logger;
}
void logTransfer(Account* from, Account* to, const double amount) {
if(logger)
logger->logTransfer(from, to, amount);
}
private:
Logger<Account*>* logger;
};
/////////////////////////////////////////////
int main() {
// try with long input
ConsoleLogger<long> console_logger;
Bank<long> bank;
bank.setLogger(&console_logger);
bank.logTransfer(500L, 1000L, 23.56);
// try with Account input
CheckingAccount* a = new CheckingAccount{500};
CheckingAccount* b = new CheckingAccount{1000};
printf("Account no.%ld\n", a->getId());
printf("Account no.%ld\n", b->getId());
ConsoleLogger<CheckingAccount*> console_logger2;
Bank<Account*> bank2;
bank2.setLogger(&console_logger2);
bank2.logTransfer(a, b, 42.81);
delete a;
delete b;
}
O compilador produz:
main.cpp: In function ‘int main()’:
main.cpp:94:19: error: cannot convert ‘ConsoleLogger*’ to ‘Logger*’
94 | bank2.setLogger(&console_logger2);
| ^~~~~~~~~~~~~~~~
| |
| ConsoleLogger<CheckingAccount*>*
main.cpp:65:36: note: initializing argument 1 of ‘void Bank::setLogger(Logger*)’
65 | void setLogger(Logger<Account*>* new_logger) {
| ~~~~~~~~~~~~~~~~~~^~~~~~~~~~
main.cpp: In instantiation of ‘void ConsoleLogger<T>::logTransfer(T, T, double) [with T = CheckingAccount*]’:
main.cpp:35:8: required from here
main.cpp:36:27: warning: format ‘%ld’ expects argument of type ‘long int’, but argument 2 has type ‘CheckingAccount*’ [-Wformat=]
36 | printf("[console] %ld -> %ld: %f\n", from, to, amount);
| ~~^ ~~~~
| | |
| long int CheckingAccount*
Resumindo, o compilador não vê a especialização do modelo para ConsoleLogger<Account*>
.
Tentei me especializar Logger<Account*>
diretamente de Logger<T>
, e derivar ConsoleLogger<Account*>
dele, mas recebi a mesma mensagem de erro do compilador.
ConsoleLogger<CheckingAccount*>
(o que você passa parasetLogger
) não tem relação comLogger<Account*>
(o quesetLogger
precisa). Você pode converterConsoleLogger<CheckingAccount*>
paraLogger<CheckingAccount*>
, mas é só isso. tambémLogger<CheckingAccount*>
não tem relação com .Logger<Account*>
No seu exemplo, você realmente não precisa
ConsoleLogger<CheckingAccount*>
porque o registrador não usa coisas específicas da conta corrente. Então, mudandopara
deve funcionar.