【问题标题】:Method forwarding with composition instead of inheritance (using C++ traits)使用组合而不是继承的方法转发(使用 C++ 特征)
【发布时间】:2015-04-08 05:08:22
【问题描述】:

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

这个想法是使用特征来确定一个方法是否被声明为 {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++ 特征来检查对象接口:Composition: using traits to avoid forwarding functions?

** 灵感来自@David Stone 的一系列 cmets:When should I use C++ private inheritance?

【问题讨论】:

    标签: c++ templates inheritance metaprogramming composition


    【解决方案1】:

    让我们从解决方案开始,逐个解释。

    #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 规范非常复杂,因为您需要处理函数调用以及构造返回值。如果你转发的函数返回一个引用,你就知道它是安全的(引用总是可以从同一类型构造的 noexcept ),但是如果函数是按值返回的,你需要确保对象的移动构造函数是 noexcept。

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

    我们使用函数体中的 static_cast 来正确处理向包含的类型添加 cv 和引用限定符。这不会被函数上的引用限定符自动拾取。

    使用继承而不是组合

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

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

    这样做的好处是

    • 可读性
    • 更快的编译时间
    • 调试版本中的代码更快(无需内联)
    • 没有用完你的 constexpr 递归深度
    • 没有用完您的模板实例化深度
    • 使用按值返回的不可移动类型
    • 使用转发给构造函数

    【讨论】:

    • 感谢您的出色回答!我学到了很多关于元编程的知识。我们可以这样使用它吗? FORWARDING_MEMBER_FUNCTIONS(std::decay&lt;decltype(inner)&gt;::type, inner, function) 并带有成员指针:FORWARDING_MEMBER_FUNCTIONS(std::decay&lt;decltype(*ptr)&gt;::type, *ptr, function); ?
    • 顺便说一句,是什么阻止了 C++ 为组合提供相同的语法 use inner.function;
    • @Julien__ 您可以更改宏以接受参数的类型。缺点是您必须在调用 FORWARDING_MEMBER_FUNCTIONS 之前进行变量声明,而如果您明确指定类型,则不存在该限制。哪个更好取决于你。但是,由于引用限定符,您可能不想将其与指针一起使用。没有什么能阻止 C++ 使用 using 语法来转发成员的函数,除非你必须说服委员会接受它。
    • @DavidStone 从 bitbucket 存储库中复制代码并在 ideone 编译会导致编译错误,除非您注释掉大部分 FORWARDING_MEMBER_FUNCTION 以供参考... webcompiler.cloudapp.net 对我来说更加神秘。我不明白从第二个 noexcept ... 开始的部分的用途是什么,以及 is_reference .. 或 is_nothrow ..
    • @xerion:Visual Studio 在其 C++14 一致性方面仍然远远落后。示例代码依赖于 constexpr、成员函数的引用限定符和普通函数的自动返回类型推导,直到 Visual Studio 2015(尚未发布)才支持这些。我不知道它的编译使用的是什么版本的 gcc ideone。在 C++14 中使用任何东西时,理想情况下你会希望使用最新版本的 clang,或者至少是最新版本的 gcc。
    猜你喜欢
    • 2015-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多