#c #stdmap #std-function
#c #stdmap #std-функция
Вопрос:
Я хочу создать std ::map для набора функций, использующих базовый объект, а также перегруженных для определенного производного класса. В случае, если функция не была перегружена, настройка будет следующей:
#include <functional>
bool func_1( const Baseamp; );
bool func_2( const Baseamp; );
std::map< std::string, std::function< bool (const Baseamp;) > > funcMap = {
{ "a", func_1 },
{ "b", func_2 }
};
Это работает без проблем. Но теперь, если бы у меня был класс, производный от Base :
class Derived : public Base {
...
}
и добавьте перегруженную версию «func_1»:
bool func_1( const Baseamp; );
bool func_2( const Baseamp; );
bool func_1( const Derivedamp; );
std::map< std::string, std::function< bool (const Baseamp;) > > funcMap = {
{ "a", func_1 },
{ "b", func_2 }
};
он больше не будет компилироваться, выдавая ошибку:
error: could not convert ‘{{"a", func_1},{"b", func_2}}’ from ‘<brace-enclosed initializer list>’ to ‘std::map<std::__cxx11::basic_string<char>, std::function<bool(const Baseamp;)> >’
Наивно я бы предположил, что, поскольку map определяется с помощью аргумента шаблона std::function< bool (const Baseamp;) >, он сможет правильно сопоставить объект func_1 с объектом, принимающим базовый аргумент, но, видимо, это не так. Есть ли способ исправить или обойти эту проблему?
Комментарии:
1. То, что вы пытаетесь сделать, небезопасно. Что делать, если кто-то получает другой тип из
Base
передачи этого объекта вашему методу, теперь запрашивает acceptBaseamp;
, но ожидает aDerivedamp;
и на самом деле получает что-то еще. Как это будет вести себя во время выполнения?2. Частью типа функции является тип ее аргументов. Я не думаю, что есть какой-либо способ обойти это, даже с шаблонами.
3. Танвир, я не совсем уверен, что ты имеешь в виду. В фактической настройке func_1( const Baseamp; ) всегда возвращает правильный результат, но может использовать ярлык, если известно, что объект относится к определенному производному классу.
4. И ответить на отметку. Я понимаю, что объект производного типа также имеет базовый тип, но при простом определении func_1 и его перегруженной версии и их использовании компилятор, похоже, способен правильно определять типы. Поэтому мне интересно, может ли кто-нибудь объяснить разницу между этим и аргументом шаблона для map в этой ситуации.
Ответ №1:
Вы могли бы использовать функцию-оболочку для вызова плоской функции, которая ожидает, что ей будет передан производный экземпляр. Оболочка просто выполняет dynamic_cast (или static_cast) и вызывает нужную функцию.
bool func_1( const Baseamp; );
bool func_2( const Baseamp; );
bool func_3( const Derivedamp; );
bool func_3_wrapper(const Base amp;b) {
const Base *pB = amp;b;
const Derived *pD = dynamic_cast<D*>(pB);
return pD ? func_3(*pD) : return false;
}
std::map< std::string, std::function< bool (const Baseamp;) > > funcMap = {
{ "a", func_1 },
{ "b", func_2 },
{ "c", func_3_wrapper }
};
В качестве альтернативы, поскольку ваши значения map являются экземплярами std::function, вы могли бы использовать лямбда.
std::map< std::string, std::function< bool (const Baseamp;) > > funcMap = {
{ "a", func_1 },
{ "b", func_2 },
{ "c", [](const Base amp;b)->bool {
auto pD = dynamic_cast<const Derived*>(amp;b);
return pD ? func_3(*pD) : false;
}}
};
Комментарии:
1. Я рад, что вы добавили обычную версию
func_3_wrapper
, я собирался спросить, почему вы использовали лямбда-выражение там, где оно явно не было нужно.
Ответ №2:
Вам нужно будет привести указатель на функцию к нужному вам типу, и компилятор выберет правильную перегрузку для типа, к которому вы приводите:
{"a", (bool(*)(const Baseamp;))func_1 },
Это не так чисто, и синтаксис указателя на функцию может сбивать с толку, но это то, что необходимо для работы этого кода
Я не понимал, что c-cast будет компилироваться, даже если нет функции, которая фактически соответствует подписи, поэтому это решение безопаснее:
{"a", static_cast<bool(*)(const Baseamp;)>(func_1)}
Кроме того, пока мы этим занимаемся, вы также можете использовать лямбда:
{"a", [](const Base amp; o) { return func_1(o); } }
Комментарии:
1. Не уверен, зачем нужен указатель, поскольку я в первую очередь передавал ссылки на функции?
2. На самом деле передача функций выполняется только с помощью указателей на функции. Когда вы передаете здесь имя функции, компилятор преобразует его в указатель на функцию для вас. Однако компилятор обрабатывает приведение к типу функции и приведение к указателю на тип функции по-разному, и указатель на функцию может быть приведен только к указателю на тип функции.
3. @W.Verbeke подобно массивам, функции постоянно распадаются на указатели.
4. Хорошо, интересно, не знал этого. Спасибо за объяснение!
5. Вместо этого я бы использовал a
static_cast
. При использовании c-cast наименьшая опечатка может иметь плохие последствия, особенно для указателей на функции