Как построить std :: сопоставление с перегруженными функциями для производного класса

#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 передачи этого объекта вашему методу, теперь запрашивает accept Baseamp; , но ожидает a Derivedamp; и на самом деле получает что-то еще. Как это будет вести себя во время выполнения?

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 наименьшая опечатка может иметь плохие последствия, особенно для указателей на функции