指向模板上下文中的成员函数或静态函数的指针
Pointer to member function or to static function in template context
我在通用上下文中使用指向成员函数的指针,它工作正常。
struct Mock{
static int inc(int){
return 0;
}
static int dec(int){
return 0;
}
};
struct Real{
Real(int v) : v(v){}
int inc(int a) const{
return a + v;
}
int dec(int a) const{
return a - v;
}
private:
int v;
};
template<typename C, typename F>
auto user(C &c, F func){
return (c.*func)(5);
}
int main(){
Real real(5);
return user(real, &Real::inc);
}
但是,如果我尝试传递静态方法(在 Mock
的情况下),它将停止工作,因为静态方法就像普通函数一样。
我需要在 user
函数中更改什么,以便编译并正常工作?
我可以用 lambda 做到这一点,但它的样板代码要多得多。
我正在考虑 SFINAE 或 constexpr if,但我不确定如何检测方法是否是静态的。
int main(){
Mock real;
return user(real, &Mock::inc);
}
我正在使用 C++17。
有过载和 SFINAE:
template<typename C, typename F>
auto user(C& c, F func) -> decltype((c.*func)(5)) {
return (c.*func)(5);
}
template<typename C, typename F>
auto user(C&, F func) -> decltype(func(5)) {
return func(5);
}
使用 if constexpr
,你可以这样做:
template<typename C, typename F>
auto user([[maybe_unused]]C& c, F func) {
if constexpr (std::is_invocable_v<F, C, int>) {
return std::invoke(func, c, 5);
} else {
static_assert(std::is_invocable_v<F, int>);
return std::invoke(func, 5);
}
}
std::is_member_pointer
可用于检测指向成员的指针。然后你可以做一个简单的 if constexpr
来改变那些和应该只接受参数的可调用之间的行为。
template<typename C, typename F>
auto user(C &c, F func){
if constexpr (std::is_member_pointer_v<F>)
return (c.*func)(5);
else
return func(5);
}
或者,如果您稍微调整一下 Mock
struct Mock{
static int inc(Mock const&, int){
return 0;
}
static int dec(Mock const&, int){
return 0;
}
};
那么你可以简单地使用std::invoke
template<typename C, typename F>
auto user(C &c, F func){
return std::invoke(func, c, 5);
}
我的 5 美分。
这是我在阅读@StoryTeller 的回答后得出的结论:
#include <type_traits>
#include <functional>
namespace class_invoke_impl_{
template <class T, class F, class... Args>
constexpr auto class_invoke_(T &&cl, F func, std::true_type, Args&&... args){
return (std::forward<T>(cl).*func)(std::forward<Args>(args)...);
}
template <class T, class F, class... Args>
constexpr auto class_invoke_(T const &, F func, std::false_type, Args&&... args){
return func(std::forward<Args>(args)...);
}
}
template <class T, class F, class... Args>
constexpr auto class_invoke(T &&cl, F func, Args&&... args){
using namespace class_invoke_impl_;
return class_invoke_(std::forward<T>(cl), func, std::is_member_pointer<F>{}, std::forward<Args>(args)...);
}
我在通用上下文中使用指向成员函数的指针,它工作正常。
struct Mock{
static int inc(int){
return 0;
}
static int dec(int){
return 0;
}
};
struct Real{
Real(int v) : v(v){}
int inc(int a) const{
return a + v;
}
int dec(int a) const{
return a - v;
}
private:
int v;
};
template<typename C, typename F>
auto user(C &c, F func){
return (c.*func)(5);
}
int main(){
Real real(5);
return user(real, &Real::inc);
}
但是,如果我尝试传递静态方法(在 Mock
的情况下),它将停止工作,因为静态方法就像普通函数一样。
我需要在 user
函数中更改什么,以便编译并正常工作?
我可以用 lambda 做到这一点,但它的样板代码要多得多。
我正在考虑 SFINAE 或 constexpr if,但我不确定如何检测方法是否是静态的。
int main(){
Mock real;
return user(real, &Mock::inc);
}
我正在使用 C++17。
有过载和 SFINAE:
template<typename C, typename F>
auto user(C& c, F func) -> decltype((c.*func)(5)) {
return (c.*func)(5);
}
template<typename C, typename F>
auto user(C&, F func) -> decltype(func(5)) {
return func(5);
}
使用 if constexpr
,你可以这样做:
template<typename C, typename F>
auto user([[maybe_unused]]C& c, F func) {
if constexpr (std::is_invocable_v<F, C, int>) {
return std::invoke(func, c, 5);
} else {
static_assert(std::is_invocable_v<F, int>);
return std::invoke(func, 5);
}
}
std::is_member_pointer
可用于检测指向成员的指针。然后你可以做一个简单的 if constexpr
来改变那些和应该只接受参数的可调用之间的行为。
template<typename C, typename F>
auto user(C &c, F func){
if constexpr (std::is_member_pointer_v<F>)
return (c.*func)(5);
else
return func(5);
}
或者,如果您稍微调整一下 Mock
struct Mock{
static int inc(Mock const&, int){
return 0;
}
static int dec(Mock const&, int){
return 0;
}
};
那么你可以简单地使用std::invoke
template<typename C, typename F>
auto user(C &c, F func){
return std::invoke(func, c, 5);
}
我的 5 美分。
这是我在阅读@StoryTeller 的回答后得出的结论:
#include <type_traits>
#include <functional>
namespace class_invoke_impl_{
template <class T, class F, class... Args>
constexpr auto class_invoke_(T &&cl, F func, std::true_type, Args&&... args){
return (std::forward<T>(cl).*func)(std::forward<Args>(args)...);
}
template <class T, class F, class... Args>
constexpr auto class_invoke_(T const &, F func, std::false_type, Args&&... args){
return func(std::forward<Args>(args)...);
}
}
template <class T, class F, class... Args>
constexpr auto class_invoke(T &&cl, F func, Args&&... args){
using namespace class_invoke_impl_;
return class_invoke_(std::forward<T>(cl), func, std::is_member_pointer<F>{}, std::forward<Args>(args)...);
}