使用组合而不是继承的方法转发(使用C ++ traits) [英] Method forwarding with composition instead of inheritance (using C++ traits)

查看:267
本文介绍了使用组合而不是继承的方法转发(使用C ++ traits)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想使用组合,并使用C ++能力为每一个可能的重载(noexcept,const,volatile)写好的转发方法。



traits为了确定一个方法是否被声明为{noexcept / const / volatile / etc}并且相应的行为。



这里是一个例子我想要实现:

  struct User {
UsedObject& obj;
User(UsedObject& obj):obj(obj){}

FORWARD_METHOD(obj,get); //这里是转发发生的地方
};

struct UsedObject {
string m {Hello\\\
};

string& get(double d){
cout<< \tUsed:const not called ... \\\
;
return m;
}
const string& get(double d)const {
cout<< \tUsed:const called ... \\\
;
returns m;
}
};

这里是我到目前为止**:

  //转发noexcept属性
//我不是100%肯定: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)...))

std :: is_nothrow_move_constructible< decltype(std :: declval< std :: add_lvalue_reference& (obj)> :: type>()。get(std :: forward< Args>(args ...))> :: value

{
cout ;& const called ... \\\
;
return obj.get(std :: forward< Args>(args)...);
}

//使用noexcept和const属性转发
//我不确定这个行为是否正确。

模板< typename ... Args>
constexpr decltype(auto)get(Args& ... args)
const noexcept(
noexcept(std :: declval< std :: add_const< decltype(obj)& > :: type>()。get(std :: forward< Args>(args)...))

std :: is_nothrow_move_constructible< decltype(std :: declval< std: :add_const< decltype(obj)&> :: type>()。get(std :: forward< Args>(args)...))> :: value

{
cout<< const not called ... \\\
;
使用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来检查对象接口:组成:使用traits避免转发功能?



**受@David Stone的评论的启发:我应该何时使用C ++私人继承?

 解决方案

> #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)...))和\ $​​ b $ b(\
std :: is_reference< return_type> :: value或\
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 (内部,内部,函数,const引用)\
FORWARDING_MEMBER_FUNCTION(内部,内部,函数,volatile引用)\
FORWARDING_MEMBER_FUNCTION b $ b #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规范非常复杂,只是因为你需要处理函数调用以及构造返回值。如果你正在转发的函数返回一个引用,你知道它是安全的(引用总是noexcept可以从同一类型构造),但如果函数返回的值,你需要确保对象的移动构造函数是noexcept。



我们可以通过使用默认的模板参数return_type来简化这一点,否则我们必须将返回类型拼写出来两次。



我们使用函数正文中的static_cast来处理正确地添加cv和引用限定符到包含的类型。



$ b

使用继承而不是组合 $ b

使用私有继承,解决方案看起来更像这样:

  struct Outer:private Inner {
使用Inner :: f;
};

这具有

的优点


  • 可读性

  • 更快的编译时间

  • 在调试版中更快的代码(无需内联)

  • 不使用constexpr递归深度

  • 不使用您的模板实例化深度

  • 按值处理返回的不可移动类型


I would like to use composition and to write good forwarding methods for every possible overload (noexcept, const, volatile) using C++ capabilities.

The idea is to use traits in order to determine whether a method is declared {noexcept / const / volatile / etc.} and to behave accordingly.

Here is an example of what I would like to achieve :

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;
    }
};

Here is what I have so far** :

// 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)...);
}

Please note that this question is different from the following one, because I know that we can use c++ traits in order to inspect an object interface : Composition: using traits to avoid forwarding functions?

** inspired by a thread of comments with @David Stone here : When should I use C++ private inheritance?.

解决方案

Let's start with the solution and explain it piece by piece.

#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 represents the type of the object you are forwarding to, and inner represents its name. Qualifiers is the combination of const, volatile, &, and && that you need on your member function.

The noexcept specification is surprisingly complicated just because you need to handle the function call as well as constructing the return value. If the function you are forwarding returns a reference, you know it is safe (references are always noexcept constructible from the same type), but if the function returned by value, you need to make sure that object's move constructor is noexcept.

We were able to simplify this a little bit by using a defaulted template argument return_type, otherwise we would have had to spell out that return type twice.

We use the static_cast in the body of the function to handle properly adding cv and reference qualifiers to the contained type. This is not automatically picked up by reference qualifiers on the function.

Using inheritance instead of composition

Using private inheritance, the solution looks more like this:

struct Outer : private Inner {
    using Inner::f;
};

This has the advantage of

  • Readability
  • Faster compile times
  • Faster code in debug builds (nothing to inline)
  • Not using up your constexpr recursion depth
  • Not using up your template instantiation depth
  • Working with returning non-movable types by value

这篇关于使用组合而不是继承的方法转发(使用C ++ traits)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆