在使用指向 const 和非常量方法的成员指针时减少模板特化的数量
reduce number of template specializations while using member pointer to const and non-const method
我有一些模板代码,它采用指向 class 的共享指针并调用函数或方法。问题来了,如果调用的方法定义为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 成员指针的特化?
在 C++17 中,我会使用带有 if constexpr
的单个模板化函数,并检查我是否可以将 f
作为带有 std::is_invocable
or not, and then use 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
的内容)。
你可能会:
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++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)...);
}
发布一个不同的答案,因为这与第一个完全不同,而且两者都很有趣(在我看来)。
我有一些模板代码,它采用指向 class 的共享指针并调用函数或方法。问题来了,如果调用的方法定义为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 成员指针的特化?
在 C++17 中,我会使用带有 if constexpr
的单个模板化函数,并检查我是否可以将 f
作为带有 std::is_invocable
or not, and then use 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
的内容)。
你可能会:
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++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)...);
}
发布一个不同的答案,因为这与第一个完全不同,而且两者都很有趣(在我看来)。