使用组合而不是继承的方法转发(使用 C++ 特性)
Method forwarding with composition instead of inheritance (using C++ traits)
我想使用组合并使用 C++ 功能为每个可能的重载(noexcept、const、volatile)编写良好的转发方法。
想法是使用 traits 来确定方法是否被声明为 {noexcept / const / volatile / etc.} 并相应地进行操作。
这是我想要实现的示例:
struct User{
UsedObject& obj;
User(UsedObject& obj) : obj(obj) {}
FORWARD_METHOD(obj, get); //here is where the forwarding happens
};
struct UsedObject{
string m{"Hello\n"};
string& get(double d){
cout << "\tUsed :const not called...\n";
return m;
}
const string& get(double d) const{
cout << "\tUsed :const called...\n";
return m;
}
};
这是我目前所拥有的**:
// forward with noexcept attribute
// I'm not 100% sure about : std::declval<std::add_lvalue_reference<decltype(obj)>::type
template<typename... Args>
constexpr decltype(auto) get(Args && ... args)
noexcept(
noexcept(std::declval<std::add_lvalue_reference<decltype(obj)>::type>().get( std::forward<Args>(args)... ))
and
std::is_nothrow_move_constructible<decltype( std::declval<std::add_lvalue_reference<decltype(obj)>::type>().get( std::forward<Args>(args)... ) )>::value
)
{
cout << "const called...\n";
return obj.get(std::forward<Args>(args)...);
}
// forward with noexcept and const attributes
// I'm not sure that this one behave properly.
template<typename... Args>
constexpr decltype(auto) get(Args && ... args)
const noexcept(
noexcept(std::declval< std::add_const<decltype(obj) &>::type >().get( std::forward<Args>(args)... ))
and
std::is_nothrow_move_constructible<decltype( std::declval< std::add_const<decltype(obj) &>::type >().get( std::forward<Args>(args)... ) )>::value
)
{
cout << "const not called...\n";
using const_type = std::add_lvalue_reference<std::add_const<std::remove_reference<decltype(obj)>::type>::type>::type;
return const_cast<const_type>(obj).get(std::forward<Args>(args)...);
}
请注意这个问题与下面的问题不同,因为我知道我们可以使用 c++ traits 来检查对象接口:Composition: using traits to avoid forwarding functions?
** 灵感来自@David Stone 的一系列评论:When should I use C++ private inheritance?.
下面先从解决方案开始,逐条讲解。
#define FORWARDING_MEMBER_FUNCTION(Inner, inner, function, qualifiers) \
template< \
typename... Args, \
typename return_type = decltype(std::declval<Inner qualifiers>().function(std::declval<Args &&>()...)) \
> \
constexpr decltype(auto) function(Args && ... args) qualifiers noexcept( \
noexcept(std::declval<Inner qualifiers>().function(std::forward<Args>(args)...)) and \
( \
std::is_reference<return_type>::value or \
std::is_nothrow_move_constructible<return_type>::value \
) \
) { \
return static_cast<Inner qualifiers>(inner).function(std::forward<Args>(args)...); \
}
#define FORWARDING_MEMBER_FUNCTIONS_CV(Inner, inner, function, reference) \
FORWARDING_MEMBER_FUNCTION(Inner, inner, function, reference) \
FORWARDING_MEMBER_FUNCTION(Inner, inner, function, const reference) \
FORWARDING_MEMBER_FUNCTION(Inner, inner, function, volatile reference) \
FORWARDING_MEMBER_FUNCTION(Inner, inner, function, const volatile reference)
#define FORWARDING_MEMBER_FUNCTIONS(Inner, inner, function) \
FORWARDING_MEMBER_FUNCTIONS_CV(Inner, inner, function, &) \
FORWARDING_MEMBER_FUNCTIONS_CV(Inner, inner, function, &&)
Inner代表你转发对象的类型,inner代表它的名字。限定符是成员函数所需的 const、volatile、& 和 && 的组合。
noexcept 规范非常复杂,因为您需要处理函数调用以及构造 return 值。如果你转发的函数 return 是一个引用,你知道它是安全的(引用总是 noexcept 可以从相同类型构造),但是如果函数 return 按值编辑,你需要确保对象的移动构造函数是 noexcept.
我们可以通过使用默认的模板参数 return_type 稍微简化一下,否则我们将不得不拼写 return 两次。
我们在函数体中使用 static_cast 来处理向包含的类型正确添加 cv 和引用限定符。这不会被函数的引用限定符自动拾取。
使用继承代替组合
使用私有继承,解决方案看起来更像这样:
struct Outer : private Inner {
using Inner::f;
};
这样做的好处是
- 可读性
- 更快的编译时间
- 调试构建中的代码速度更快(无需内联)
- 没有用完你的 constexpr 递归深度
- 没有用完你的模板实例化深度
- 按值处理return不可移动类型
- 处理转发给构造函数
我想使用组合并使用 C++ 功能为每个可能的重载(noexcept、const、volatile)编写良好的转发方法。
想法是使用 traits 来确定方法是否被声明为 {noexcept / const / volatile / etc.} 并相应地进行操作。
这是我想要实现的示例:
struct User{
UsedObject& obj;
User(UsedObject& obj) : obj(obj) {}
FORWARD_METHOD(obj, get); //here is where the forwarding happens
};
struct UsedObject{
string m{"Hello\n"};
string& get(double d){
cout << "\tUsed :const not called...\n";
return m;
}
const string& get(double d) const{
cout << "\tUsed :const called...\n";
return m;
}
};
这是我目前所拥有的**:
// forward with noexcept attribute
// I'm not 100% sure about : std::declval<std::add_lvalue_reference<decltype(obj)>::type
template<typename... Args>
constexpr decltype(auto) get(Args && ... args)
noexcept(
noexcept(std::declval<std::add_lvalue_reference<decltype(obj)>::type>().get( std::forward<Args>(args)... ))
and
std::is_nothrow_move_constructible<decltype( std::declval<std::add_lvalue_reference<decltype(obj)>::type>().get( std::forward<Args>(args)... ) )>::value
)
{
cout << "const called...\n";
return obj.get(std::forward<Args>(args)...);
}
// forward with noexcept and const attributes
// I'm not sure that this one behave properly.
template<typename... Args>
constexpr decltype(auto) get(Args && ... args)
const noexcept(
noexcept(std::declval< std::add_const<decltype(obj) &>::type >().get( std::forward<Args>(args)... ))
and
std::is_nothrow_move_constructible<decltype( std::declval< std::add_const<decltype(obj) &>::type >().get( std::forward<Args>(args)... ) )>::value
)
{
cout << "const not called...\n";
using const_type = std::add_lvalue_reference<std::add_const<std::remove_reference<decltype(obj)>::type>::type>::type;
return const_cast<const_type>(obj).get(std::forward<Args>(args)...);
}
请注意这个问题与下面的问题不同,因为我知道我们可以使用 c++ traits 来检查对象接口:Composition: using traits to avoid forwarding functions?
** 灵感来自@David Stone 的一系列评论:When should I use C++ private inheritance?.
下面先从解决方案开始,逐条讲解。
#define FORWARDING_MEMBER_FUNCTION(Inner, inner, function, qualifiers) \
template< \
typename... Args, \
typename return_type = decltype(std::declval<Inner qualifiers>().function(std::declval<Args &&>()...)) \
> \
constexpr decltype(auto) function(Args && ... args) qualifiers noexcept( \
noexcept(std::declval<Inner qualifiers>().function(std::forward<Args>(args)...)) and \
( \
std::is_reference<return_type>::value or \
std::is_nothrow_move_constructible<return_type>::value \
) \
) { \
return static_cast<Inner qualifiers>(inner).function(std::forward<Args>(args)...); \
}
#define FORWARDING_MEMBER_FUNCTIONS_CV(Inner, inner, function, reference) \
FORWARDING_MEMBER_FUNCTION(Inner, inner, function, reference) \
FORWARDING_MEMBER_FUNCTION(Inner, inner, function, const reference) \
FORWARDING_MEMBER_FUNCTION(Inner, inner, function, volatile reference) \
FORWARDING_MEMBER_FUNCTION(Inner, inner, function, const volatile reference)
#define FORWARDING_MEMBER_FUNCTIONS(Inner, inner, function) \
FORWARDING_MEMBER_FUNCTIONS_CV(Inner, inner, function, &) \
FORWARDING_MEMBER_FUNCTIONS_CV(Inner, inner, function, &&)
Inner代表你转发对象的类型,inner代表它的名字。限定符是成员函数所需的 const、volatile、& 和 && 的组合。
noexcept 规范非常复杂,因为您需要处理函数调用以及构造 return 值。如果你转发的函数 return 是一个引用,你知道它是安全的(引用总是 noexcept 可以从相同类型构造),但是如果函数 return 按值编辑,你需要确保对象的移动构造函数是 noexcept.
我们可以通过使用默认的模板参数 return_type 稍微简化一下,否则我们将不得不拼写 return 两次。
我们在函数体中使用 static_cast 来处理向包含的类型正确添加 cv 和引用限定符。这不会被函数的引用限定符自动拾取。
使用继承代替组合
使用私有继承,解决方案看起来更像这样:
struct Outer : private Inner {
using Inner::f;
};
这样做的好处是
- 可读性
- 更快的编译时间
- 调试构建中的代码速度更快(无需内联)
- 没有用完你的 constexpr 递归深度
- 没有用完你的模板实例化深度
- 按值处理return不可移动类型
- 处理转发给构造函数