使用组合而不是继承的方法转发(使用 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不可移动类型
  • 处理转发给构造函数