C++11/14:包装一个函数(如果存在)
C++11/14: Wrap a function if it exists
我想编写一个 wrapper
class(非常像一个代理)来聚合一个对象,并将成员函数调用转发给它。在使用可变参数模板和 decltype
的 C++11/14 中,这很简单。我的问题是包装对象可能支持也可能不支持某些成员函数。
我想出了一个似乎可行的解决方案,但是,它看起来非常笨拙,我正在寻找简化方法。特别是我担心这在编译时可能会非常昂贵(有 many 函数要包装)。这种笨拙来自于需要指定函数的 return 类型,而无需 decltype 某些令人窒息的东西。
有人有更好的主意吗?
下面这段代码也可用live.
#include <iostream>
#include <utility>
/// Compute the result type of a member function call, or void if invalid.
#define RESULT_OF(Name) \
template <typename T> \
class result_impl_ ## Name \
{ \
public: \
/* Type made public to please Clang 3.7. */ \
template <typename C, typename... Args> \
static auto Type(void*) \
-> decltype(std::declval<C>().Name(std::declval<Args>()...)); \
\
template <typename, typename...> \
static void Type(...); \
\
template <typename... Args> \
using type = decltype(Type<T, Args...>(0)); \
}; \
\
template <typename T, typename... Args> \
using maybe_result_of_ ## Name \
= typename result_impl_ ## Name<T>::template type<Args...>
/// Forward to function Name, if is exists.
#define FORWARD(Name) \
template <typename... Args> \
auto Name(Args&&... args) \
-> maybe_result_of_ ## Name<Base, Args...> \
{ \
return base.Name(std::forward<Args>(args)...); \
}
#define DEFINE(Name) \
RESULT_OF(Name); \
FORWARD(Name)
template <typename Base>
struct wrapper
{
Base base;
DEFINE(foo);
DEFINE(bar);
};
#define PING() \
std::cerr << __PRETTY_FUNCTION__ << '\n'
struct foo_no_bar
{
void foo(int) const { PING(); }
int foo(double) const { PING(); return 1; }
int foo(double, double) const { PING(); return 1; }
};
struct foo_and_bar
{
void foo() const { PING(); }
void bar() { PING(); }
};
int main()
{
wrapper<foo_and_bar> f;
f.foo();
f.bar();
wrapper<foo_no_bar> b;
b.foo(1);
b.foo(1.0);
b.foo(1.0, 2.0);
}
所以我把你在宏中做的一堆工作拿出来了。
can_apply_t
接受一个 template<class...>class
和一组类型,如果类型可以合法地应用于模板,则为真。
template<class...>struct voider { using type=void; };
template<class...Ts>using void_t=typename voider<Ts...>::type;
template<class...>struct types{ using type=types; };
namespace details {
template<template<class...>class Z, class types, class=void>
struct can_apply : std::false_type {};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, types<Ts...>, void_t< Z<Ts...> > >:
std::true_type
{};
}
template<template<class...>class Z, class...Ts>
using can_apply_t = details::can_apply<Z, types<Ts...>>;
然后我们开始替换您的宏。我将方法名称与对象名称分离,并分几步完成。对于每种方法,这些步骤可以在 class 的 之外 完成:
#define CALL_METHOD_RESULT(Name, Method) \
template<class Sig, class=void> \
struct Name {}; \
template<class C, class...Ts> \
struct Name< C(Ts...), void_t< \
decltype( std::declval<C>().Method(std::declval<Ts>()...) ) \
>> { \
using type = decltype( std::declval<C>().Method(std::declval<Ts>()...) ); \
}; \
template<class Sig> \
using Name ## _t = typename Name< Sig >::type
上面定义了 Name_t
,它是一个特征 class,它带有 Object(Args...)
并告诉你 return 类型的 Object.Method(Args...)
在 SFINAE 友好上下文。
接下来,我们使用这个和上面的 can_apply_t
:
构建一个 can_call_Method
辅助模板
#define CAN_CALL_METHOD( Method ) \
CALL_METHOD_RESULT( call_ ## Method, Method ); \
template<class Sig> \
using can_call_ ## Method = can_apply_t< call_ ## Method ## _t, Sig >
接下来是一个FORWARD
宏,为Name
生成一对重载,其中一个调用target.Method(Args...)
where Target target
,另一个只考虑如果第一个不是并且明确地 =delete
s 调用生成可能更好的错误消息。
/// Forward to function Name, if is exists.
#define FORWARD(Name, Method, Target, target) \
template <class... Args> \
auto Name(Args&&... args) \
-> call_ ## Method ## _t<Target(Args...)> \
{ \
return target.Method(std::forward<Args>(args)...); \
} \
template <class...Args> \
std::enable_if_t<!can_call_ ## Method <Target(Args...)>{}> \
Name ( Args&&...) = delete
现在我们将上面的内容包装在一个类似的 DEFINE 宏中。在这里,我们将所有内容重新耦合回同一个名称:
#define DEFINE(Name) \
CAN_CALL_METHOD(Name); \
FORWARD(Name, Name, Base, base)
如果需要,我们还可以让 =delete
方法做其他事情,比如什么都不做。
但是请注意,如果我们省略整个 =delete
方法,我们实际上可以在两个子对象之间进行受控调度(即使有优先级!)
一个更简单的方法当然是
Base* operator->(){ return &base; }
Base const* operator->()const{ return &base; }
这让您可以像 wrapper->foo(whatever)
一样访问 Base
。它确实暴露了 关于 Base
.
的一切
我想编写一个 wrapper
class(非常像一个代理)来聚合一个对象,并将成员函数调用转发给它。在使用可变参数模板和 decltype
的 C++11/14 中,这很简单。我的问题是包装对象可能支持也可能不支持某些成员函数。
我想出了一个似乎可行的解决方案,但是,它看起来非常笨拙,我正在寻找简化方法。特别是我担心这在编译时可能会非常昂贵(有 many 函数要包装)。这种笨拙来自于需要指定函数的 return 类型,而无需 decltype 某些令人窒息的东西。
有人有更好的主意吗?
下面这段代码也可用live.
#include <iostream>
#include <utility>
/// Compute the result type of a member function call, or void if invalid.
#define RESULT_OF(Name) \
template <typename T> \
class result_impl_ ## Name \
{ \
public: \
/* Type made public to please Clang 3.7. */ \
template <typename C, typename... Args> \
static auto Type(void*) \
-> decltype(std::declval<C>().Name(std::declval<Args>()...)); \
\
template <typename, typename...> \
static void Type(...); \
\
template <typename... Args> \
using type = decltype(Type<T, Args...>(0)); \
}; \
\
template <typename T, typename... Args> \
using maybe_result_of_ ## Name \
= typename result_impl_ ## Name<T>::template type<Args...>
/// Forward to function Name, if is exists.
#define FORWARD(Name) \
template <typename... Args> \
auto Name(Args&&... args) \
-> maybe_result_of_ ## Name<Base, Args...> \
{ \
return base.Name(std::forward<Args>(args)...); \
}
#define DEFINE(Name) \
RESULT_OF(Name); \
FORWARD(Name)
template <typename Base>
struct wrapper
{
Base base;
DEFINE(foo);
DEFINE(bar);
};
#define PING() \
std::cerr << __PRETTY_FUNCTION__ << '\n'
struct foo_no_bar
{
void foo(int) const { PING(); }
int foo(double) const { PING(); return 1; }
int foo(double, double) const { PING(); return 1; }
};
struct foo_and_bar
{
void foo() const { PING(); }
void bar() { PING(); }
};
int main()
{
wrapper<foo_and_bar> f;
f.foo();
f.bar();
wrapper<foo_no_bar> b;
b.foo(1);
b.foo(1.0);
b.foo(1.0, 2.0);
}
所以我把你在宏中做的一堆工作拿出来了。
can_apply_t
接受一个 template<class...>class
和一组类型,如果类型可以合法地应用于模板,则为真。
template<class...>struct voider { using type=void; };
template<class...Ts>using void_t=typename voider<Ts...>::type;
template<class...>struct types{ using type=types; };
namespace details {
template<template<class...>class Z, class types, class=void>
struct can_apply : std::false_type {};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, types<Ts...>, void_t< Z<Ts...> > >:
std::true_type
{};
}
template<template<class...>class Z, class...Ts>
using can_apply_t = details::can_apply<Z, types<Ts...>>;
然后我们开始替换您的宏。我将方法名称与对象名称分离,并分几步完成。对于每种方法,这些步骤可以在 class 的 之外 完成:
#define CALL_METHOD_RESULT(Name, Method) \
template<class Sig, class=void> \
struct Name {}; \
template<class C, class...Ts> \
struct Name< C(Ts...), void_t< \
decltype( std::declval<C>().Method(std::declval<Ts>()...) ) \
>> { \
using type = decltype( std::declval<C>().Method(std::declval<Ts>()...) ); \
}; \
template<class Sig> \
using Name ## _t = typename Name< Sig >::type
上面定义了 Name_t
,它是一个特征 class,它带有 Object(Args...)
并告诉你 return 类型的 Object.Method(Args...)
在 SFINAE 友好上下文。
接下来,我们使用这个和上面的 can_apply_t
:
can_call_Method
辅助模板
#define CAN_CALL_METHOD( Method ) \
CALL_METHOD_RESULT( call_ ## Method, Method ); \
template<class Sig> \
using can_call_ ## Method = can_apply_t< call_ ## Method ## _t, Sig >
接下来是一个FORWARD
宏,为Name
生成一对重载,其中一个调用target.Method(Args...)
where Target target
,另一个只考虑如果第一个不是并且明确地 =delete
s 调用生成可能更好的错误消息。
/// Forward to function Name, if is exists.
#define FORWARD(Name, Method, Target, target) \
template <class... Args> \
auto Name(Args&&... args) \
-> call_ ## Method ## _t<Target(Args...)> \
{ \
return target.Method(std::forward<Args>(args)...); \
} \
template <class...Args> \
std::enable_if_t<!can_call_ ## Method <Target(Args...)>{}> \
Name ( Args&&...) = delete
现在我们将上面的内容包装在一个类似的 DEFINE 宏中。在这里,我们将所有内容重新耦合回同一个名称:
#define DEFINE(Name) \
CAN_CALL_METHOD(Name); \
FORWARD(Name, Name, Base, base)
如果需要,我们还可以让 =delete
方法做其他事情,比如什么都不做。
但是请注意,如果我们省略整个 =delete
方法,我们实际上可以在两个子对象之间进行受控调度(即使有优先级!)
一个更简单的方法当然是
Base* operator->(){ return &base; }
Base const* operator->()const{ return &base; }
这让您可以像 wrapper->foo(whatever)
一样访问 Base
。它确实暴露了 关于 Base
.