Функция шаблона не работает для функции указатель на член, принимающей const ref

14

В последнее время я написал шаблонную функцию для решения некоторых повторений кода. Это выглядит так:

template<class T, class R, class... Args>
R call_or_throw(const std::weak_ptr<T>& ptr, const std::string& error, R (T::*fun)(Args...), Args... args) {
    if (auto sp = ptr.lock()) 
    {
        return std::invoke(fun, *sp, args...);
    }
    else 
    {
        throw std::runtime_error(error.c_str());
    }
}

int main() {
    auto a = std::make_shared<A>();
    call_or_throw(std::weak_ptr<A>(a), "err", &A::foo, 1);
}

Этот код прекрасно работает, для class Aкоторого выглядит так:

class A {
public:
    void foo(int x) {

    }
};

Но не может скомпилировать для одного, как это:

class A {
public:
    void foo(const int& x) {

    }
};

Почему это так (почему я имею в виду, почему он не может определить тип) и как (если это вообще возможно) заставить этот код работать со ссылками? Живой пример

bartop
источник
может Args&&...и std::forward?
FAS
@ user3365922 попробовал. По ощущению как решение, не работает
bartop
Не будет ли это и это поможет вам в правильном направлении?
Gizmo

Ответы:

3

Ваша проблема заключается в том, что у вас есть конфликтные выводы Argsмежду:

  • R (T::*fun)(Args...)
  • Args... args

Я предлагаю , чтобы иметь больше общий код (отсутствие дублирования между R (T::*fun)(Args...)и
версией сопзЬ R (T::*fun)(Args...) constи другой альтернативой) с:

template<class T, class F, class... Args>
decltype(auto) call_or_throw(const std::weak_ptr<T>& ptr,
                             const std::string& error,
                             F f,
                             Args&&... args)
{
    if (auto sp = ptr.lock()) 
    {
        return std::invoke(f, *sp, std::forward<Args>(args)...);
    }
    else 
    {
        throw std::runtime_error(error.c_str());
    }
}
Jarod42
источник
хороший пункт о сорте-квалификации функции члена, я думаю , что это лучшее решение до сих пор
bartop
8

Argsтипы не могут быть выведены как const&(из funобъявления параметра), так и без ссылки из argsобъявления. Простым решением является использование двух отдельных пакетов параметров типа шаблона:

template<class T, class R, class... Args, class... DeclaredArgs>
R call_or_throw(
    const std::weak_ptr<T>& ptr,
    const std::string& error,
    R (T::*fun)(DeclaredArgs...),
    Args... args);

Как недостаток, я могу представить немного более длинные сообщения об ошибках в случае неправильного использования.

LogicStuff
источник
1
Вы, вероятно, хотитеArgs&&... args
Jarod42
5

Обратите внимание, что Argsтип параметра шаблона выводится как const int&на 3-м аргументе функции &A::foo, так и intна 4-м параметре функции 1. Они не совпадают и вызывают сбой дедукции.

Вы можете исключить 4-й параметр из вычета , например,

template<class T, class R, class... Args>
R call_or_throw(const std::weak_ptr<T>& ptr, 
                const std::string& error, 
                R (T::*fun)(Args...), 
                std::type_identity_t<Args>... args) {
//              ^^^^^^^^^^^^^^^^^^^^^^^^^^                

ЖИТЬ

PS: std::type_identityподдерживается начиная с C ++ 20; но это довольно легко реализовать.

songyuanyao
источник
1
это сработает с идеальной пересылкой?
Бароп
@ bartop Я так думаю. Мы можем сделать 4-й параметр соответствующим стилю переадресации, т. Е. Args&&...Затем добавить std::type_identity3-й параметр как R (T::*fun)(std::type_identity_t<Args>...). Прямой и Прямой
songyuanyao
@songyuanyo да, но тогда это сломается для аргумента ценности.
Бароп
Вы уже можете использовать форвард из своего кода Demo . Это просто сделает «дополнительный» ход.
Jarod42