#c #templates #design-patterns
#c #шаблоны #шаблоны проектирования
Вопрос:
Я пытаюсь создать шаблон, в котором абстрактный класс имеет несколько методов, где все получают один и тот же элемент. Производная реализация относится к определенному типу элемента, и шаблон должен исключить необходимость уменьшения типов.
Следующее не работает:
Предположим, что эти классы типов
//--------------
// Data classes
//--------------
class TypeA
{
virtual int a() = 0;
};
class TypeB : public TypeA
{
int a() override
{
return 5;
}
};
Шаблон:
//-------------------
// Base action class.
//-------------------
class ExampleBase
{
public:
virtual bool f1(TypeAamp; val) = 0;
virtual bool f2(TypeAamp; val) = 0;
};
//----------------------------------
// Base specific Typed action class.
//----------------------------------
template<class T>
class ExampleSpecific1 : public ExampleBase
{
public:
virtual bool specificF1(Tamp; specificVal) = 0;
virtual bool specificF2(Tamp; specificVal) = 0;
bool f1(TypeAamp; val) override
{
return fRunner<bool, specificF1>(val, false);
}
bool f2(TypeAamp; val) override
{
return fRunner<bool, specificF2>(val, false);
}
private:
// Run the specific function with the specific type
template<class S, S (*pf)(Tamp; val)>
S fRunner(TypeAamp; val, S defaultValue)
{
S ret = defaultValue;
Tamp; specificVal = dynamic_cast<Tamp;>(val);
if (amp;specificVal != nullptr) {
ret = pf(specificVal);
}
return ret;
}
};
Реализация
//----------------------
// Class Implementation.
//----------------------
class ExampleImpl : public ExampleSpecific1<TypeB>
{
public:
bool specificF1(TypeBamp; specificVal) override
{
// Do something
}
bool specificF2(TypeBamp; specificVal) override
{
// Do something
}
};
Использование:
//-----------
// Class Use.
//-----------
void main()
{
ExampleImpl impl;
TypeB myVal;
TypeAamp; myBaseVal = myVal;
impl.f1(myBaseVal);
impl.f2(myBaseVal);
}
Я получаю следующую ошибку компиляции:
error C2672: 'ExampleSpecific1<TypeB>::fRunner': no matching overloaded function found
note: while compiling class template member function 'bool ExampleSpecific1<TypeB>::f2(TypeA amp;)'
note: see reference to function template instantiation 'bool ExampleSpecific1<TypeB>::f2(TypeA amp;)' being compiled
note: see reference to class template instantiation 'ExampleSpecific1<TypeB>' being compiled
error C2975: 'pf': invalid template argument for 'ExampleSpecific1<TypeB>::fRunner', expected compile-time constant expression
note: see declaration of 'pf'
Шаблон функции, который работает (когда функция не находится внутри класса):
На основе предыдущего примера:
template<class T, bool (*pf1)(Tamp; Val), bool (*pf2)(Tamp; Val)>
class ExampleSpecific2 : public ExampleBase
{
public:
bool f1(TypeAamp; val) override
{
bool ret = false;
Tamp; specificVal = dynamic_cast<Tamp;>(val);
if (amp;specificVal != nullptr) {
ret = pf1(specificVal);
}
return ret;
}
bool f2(TypeAamp; val) override
{
bool ret = false;
Tamp; specificVal = dynamic_cast< Tamp;>(val);
if (amp;specificVal != nullptr) {
ret = pf2(specificVal);
}
return ret;
}
};
Внешние функции:
bool extF1(TypeBamp; val)
{
// Do something.
}
bool extF2(TypeBamp; val)
{
// Do something.
}
Использование:
//-----------
// Class Use.
//-----------
void main()
{
TypeB myVal;
TypeAamp; myBaseVal = myVal;
ExampleSpecific2<TypeB, extF1, extF2> impl2;
impl2.f1(myBaseVal);
impl2.f2(myBaseVal);
}
В примере, который не работает, я могу реализовать приведение вниз в каждой реализации, и тогда это работает, но это уродливо и не является универсальным. В рабочем примере я хочу, чтобы функция была во внутренней реализации класса, а не внешней по отношению к нему, это важно в более сложных сценариях, где базовый класс вызывает несколько производных методов.
- Кстати, мне не нравится заголовок этого поста, если у вас есть лучшее предложение, которое мне нравится, это будет здорово.
Ответ №1:
Примечание :
main
должен возвращать значение int, а не voidif (amp;specificVal != nullptr)
всегда будет true, ссылка не может быть нулевой.
Я не понимаю, почему вы получили это сообщение об ошибке, с gcc
я получил :
no matching member function for call to 'fRunner'
Потому что pf
тип был bool (ExampleSpecific1<TypeB>::*)(TypeB amp;)
, который не совпадал с S (*pf)(Tamp; val)
. Для первого нужен объект класса.
Поэтому я просто использую C 17 auto :
template<class S, auto pf>
S fRunner(TypeAamp; val, S defaultValue){...}
Но вы можете использовать полный тип, если хотите.
Далее нам нужно вызвать функцию-член. Мне не нравится (и я не помню) синтаксис вызова функции-члена, поэтому я просто использую std::invoke
из C 17. (смотрите:https://en.cppreference.com/w/cpp/utility/functional/invoke )
Live:https://wandbox.org/permlink/rEqgLSwSjEfqRK2o
#include <iostream>
#include <vector>
//--------------
// Data classes
//--------------
class TypeA
{
virtual int a() = 0;
};
class TypeB : public TypeA
{
int a() override
{
return 5;
}
};
//-------------------
// Base action class.
//-------------------
class ExampleBase
{
public:
virtual bool f1(TypeAamp; val) = 0;
virtual bool f2(TypeAamp; val) = 0;
};
//----------------------------------
// Base specific Typed action class.
//----------------------------------
template<class T>
class ExampleSpecific1 : public ExampleBase
{
private:
// Run the specific function with the specific type
template<class S, auto pf>
S fRunner(TypeAamp; val, S defaultValue)
{
S ret = defaultValue;
Tamp; specificVal = dynamic_cast<Tamp;>(val);
ret = std::invoke(pf, *this, specificVal);
return ret;
}
public:
virtual bool specificF1(Tamp; specificVal) = 0;
virtual bool specificF2(Tamp; specificVal) = 0;
bool f1(TypeAamp; val) override
{
return this->fRunner<bool, amp;ExampleSpecific1<T>::specificF1>(val, false);
}
bool f2(TypeAamp; val) override
{
return this->fRunner<bool, amp;ExampleSpecific1<T>::specificF2>(val, false);
}
};
// Class Implementation.
//----------------------
class ExampleImpl : public ExampleSpecific1<TypeB>
{
public:
bool specificF1(TypeBamp; ) override
{
std::cout << "specificF1" << std::endl;
return true;
}
bool specificF2(TypeBamp; ) override
{
std::cout << "specificF2" << std::endl;
return true;
}
};
//-----------
// Class Use.
//-----------
int main()
{
ExampleImpl impl;
TypeB myVal;
TypeAamp; myBaseVal = myVal;
impl.f1(myBaseVal);
impl.f2(myBaseVal);
}
До C 17:https://wandbox.org/permlink/HSGMy4zb4TgusESf
template<class S, S (ExampleSpecific1<T>::*pf)(T amp;)> // full type since auto is C 17
S fRunner(TypeAamp; val, S defaultValue)
{
S ret = defaultValue;
Tamp; specificVal = dynamic_cast<Tamp;>(val);
ret = (this->*pf)(specificVal); // Ugly pre 17
return ret;
}
Комментарии:
1. Это упрощенный пример, в реальной жизни основной функции вообще нет — это просто для демонстрационных целей, я годами не писал основную функцию, так что эта на мне.
amp;specificVal != nullptr
На самом деле это работает, проверял это много-много раз с faileddynaic_cast
, и я не прошел мимоif
инструкции (в любом случае, это не главное). Код работает идеально, когда методология fRunner включена непосредственно в реализациюf1
иf2
, как это сделано во втором примере (ExampleSpecific2
).2. Из en.cppreference.com/w/cpp/language/dynamic_cast : » Если приведение завершается неудачей, а new-type является ссылочным типом, он выдает исключение, соответствующее обработчику типа std::bad_cast .». В общем, ссылка не может быть нулевой, если вы
dynamic_cast
указатель, тогда да, вы должны проверить3. Исходя из этого, если мой ответ не соответствует вашим требованиям, можете ли вы привести живой пример? Я перестроил ваш код со всем, что было до «Я получаю следующую ошибку компиляции». И если это произойдет, пожалуйста, отметьте как разрешенный
4. С вашим кодом я получаю:
note: 'auto' in non-type template parameters requires at least '/std:c 17'
(Мы все еще в VS2017) — Поэтому я изменил auto на свое предыдущее определение и столкнулся с той же проблемой. Дело в том, что я получаю эту ошибку компиляции. Следовательно, единственный рабочий пример, который я могу привести, — это тот, где ‘fRunner’ не используется. Если это достаточно хорошо, я могу добавить это! Что касаетсяdynamic_cast
я проверю это, может быть, мы никогда не передаем класс, который нельзя понизить, и вы можете быть правы.5. «Итак, я изменил auto на свое предыдущее определение». AFAIU, в чем проблема. У меня было решение до 17 без
auto
norinvoke
. Скажите мне, работает ли это