Уменьшить количество специализаций шаблона при использовании указателя на метод const и неконстантный
У меня есть некоторый шаблон кода, который принимает общий указатель на класс и вызывает функцию или метод. Проблема возникает, если вызываемый метод определен как const
,
Пример:
struct Y {};
struct X
{
const Y Go() const { return Y{}; }
const Y Go2() { return Y{}; }
};
Y f1( std::shared_ptr<X> ) { return Y{}; }
template< typename FUNC, typename ... ARGS >
auto Do( std::shared_ptr<X>& ptr, FUNC&& f, ARGS&& ... args )
{
return f( ptr, std::forward<ARGS>(args)... );
}
template < typename CLASS, typename RET, typename ... ARGS>
auto Do( std::shared_ptr<X>& base_ptr, RET (CLASS::*mem_ptr)( ARGS...), ARGS&& ... args )->RET
{
return (base_ptr.get()->*mem_ptr)( std::forward<ARGS>(args)...);
}
// Any chance to avoid the full duplication of the code here
// to define the member pointer to a const method?
template < typename CLASS, typename RET, typename ... ARGS>
auto Do( std::shared_ptr<X>& base_ptr, RET (CLASS::*mem_ptr)( ARGS...) const, ARGS&& ... args )->RET
{
return (base_ptr.get()->*mem_ptr)( std::forward<ARGS>(args)...);
}
int main()
{
auto xptr = std::make_shared<X>();
Y y1 = Do( xptr, &X::Go );
Y y2 = Do( xptr, &X::Go2 );
Y y3 = Do( xptr, &f1 );
}
Моя проблема - последняя специализация с RET (CLASS::*mem_ptr)( ARGS...) const
, Я просто хочу остановить дублирование всего кода только для const. В реальном коде функция снова вызывает другой шаблонный код, что приводит к дублированию большого количества кода.
Есть ли шанс избавиться от специализации для указателя на член const?
3 ответа
Вы могли бы сделать:
template< typename FUNC, typename ... ARGS >
auto Do( std::shared_ptr<X>& ptr, FUNC&& f, ARGS&& ... args )
-> decltype((f(ptr, std::forward<ARGS>(args)... )))
{
return f( ptr, std::forward<ARGS>(args)... );
}
template<typename MemberF, typename ... ARGS>
auto Do(std::shared_ptr<X>& base_ptr, MemberF mem_ptr, ARGS&& ... args)
-> decltype((base_ptr.get()->*mem_ptr)( std::forward<ARGS>(args)...))
{
return (base_ptr.get()->*mem_ptr)( std::forward<ARGS>(args)...);
}
В C++17 я бы использовал одну шаблонную функцию с if constexpr
и проверь могу ли я позвонить f
в качестве шаблонной функции-члена с std::is_invocable
или нет, а затем использовать std::invoke
чтобы назвать это:
template< typename FUNC, typename ... ARGS >
auto Do( std::shared_ptr<X>& ptr, FUNC&& f, ARGS&& ... args ) {
if constexpr (std::is_invocable_v<FUNC, decltype(ptr), ARGS...>) {
return std::invoke(f, ptr, std::forward<ARGS>(args)... );
}
else {
return std::invoke(f, ptr.get(), std::forward<ARGS>(args)... );
}
}
До C++17 у вас может быть две перегрузки: одна для функций, не являющихся членами, и одна для функций-членов. Затем вы можете использовать SFINAE для отключения одного или другого в зависимости от типа вызываемого (используя что-то похожее на std::is_invocable
).
Вот версия C++14, которая не требует SFINAE и полагается на то, что:
const Y (X::*)()
такой же какU1 X::*
сU1 = const Y()
;connt Y (X::*)() const
такой же какU2 X::*
сU2 = const Y() const
,
template< typename FUNC, typename ... ARGS >
auto Do( std::shared_ptr<X>& ptr, FUNC&& f, ARGS&& ... args )
{
return f( ptr, std::forward<ARGS>(args)... );
}
template < typename CLASS, typename U, typename ... ARGS>
auto Do( std::shared_ptr<X>& base_ptr, U CLASS::*mem_ptr, ARGS&& ... args )
{
return (base_ptr.get()->*mem_ptr)( std::forward<ARGS>(args)...);
}
Публикация другого ответа, потому что он полностью отличается от первого, и оба интересны (на мой взгляд).