代码之家  ›  专栏  ›  技术社区  ›  bolov

使用右值引用,而不是使用可变模板转发引用

  •  2
  • bolov  · 技术社区  · 7 年前

    我有一个结构 X 和一个函数 foo 必须接收的右值引用 十、 .

    起初我只从一个论点开始,它很简单(哦……更简单的时候):

    auto foo(X&& arg) -> void {...};
    
    X x;
    foo(x);            // compile error [OK]
    foo(std::move(x)); // accepted      [OK]
    foo(X{});          // accepted      [OK]
    

    但后来我想扩展并接受可变数量的

    但有一个问题。

    • 第一,你不能有 auto foo(X&&... args)
    • 第二,现在你必须这样做 template <class... Args> auto foo(Args&&... args) 但现在,你最终会转发参考资料,这将很乐意接受非临时性:
    template <class... Args>
    auto foo(Args&&... args) -> void { ... };
    
    X x1, x2;
    foo(x1, x2);                       // accepted [NOT OK]
    foo(std::move(x1), std::move(x2)); // accepted [OK]
    foo(X{}, X{});                     // accepted [OK]
    

    他们为什么用这种语法和规则转发参考文献,从一开始我就感到困惑。这是一个问题。这种语法的另一个问题是 T&& X<T>&& 是完全不同的野兽。但我们正在偏离正轨。

    我知道如何解决这个问题 static_assert SFINAE 但这两种解决方案都让事情变得有点复杂,在我看来,如果该语言只设计了一次,那么就不应该需要它。甚至不要让我开始 std::initializer_list …我们又偏离了轨道。

    Args&& / args


    为左值引用添加已删除的重载:

    template <class... Args>
    auto foo(const Args&... args) = delete;
    
    template <class... Args>
    auto foo(Args&... args) = delete;
    

    简单,优雅,应该有用,让我们测试一下:

    X x1, x2;
    
    foo(x1, x2);                       // compile error [OK]
    foo(std::move(x1), std::move(x2)); // accepted [OK]
    foo(X{}, X{});                     // accepted [OK]
    

    foo(std::move(x1), x2);            // accepted [oh c'mon]
    
    3 回复  |  直到 7 年前
        1
  •  6
  •   Barry    7 年前

    这个 使用SFINAE可以获得大量右值引用:

    template <class... Args,
        std::enable_if_t<(!std::is_lvalue_reference<Args>::value && ...), int> = 0>
    void foo(Args&&... args) { ... }
    

    折叠表达式是C++17,在C++14中编写一个元函数以获得相同的行为很容易。这是你唯一的选择,真的-你想要一个受约束的函数模板来推断右值引用,但唯一可用的语法是重载以表示转发引用。我们可以不推导模板参数,但你必须提供它们,这似乎根本不是一个解决方案。

    template <class... Args>
        requires (!std::is_lvalue_reference<Args>::value && ...)
    void foo(Args&&... args) { ... }
    

    或者更好:

    template <class T>
    concept NonReference = !std::is_lvalue_reference<T>::value;
    
    template <NonReference... Args>
    void foo(Args&&... ) { ... }
    

    template <class... Args> auto foo(const Args&... args) = delete;
    template <class... Args> auto foo(Args&... args) = delete;
    

    全部的 任何 左值引用。

        2
  •  6
  •   skypjack    7 年前

    我知道如何用static_assertorsfinaeb解决这个问题,但这两种解决方案都有点复杂[…]有没有我遗漏的简单解决方案/技巧[…]?

    有一种很好的方法可以做到这一点,而不需要复杂的SFINAE表达式或 static_assert s、 它也不需要您猜测哪些参数是常量,哪些不是(如果您尝试处理变量的常量,它可能会很快将您引导到UB)。此外,你不必包括 <type_traits>
    @Justin's answer 就快到了。

    decltype 和一个测试函数,您只需要一个声明,根本不需要定义:

    #include<utility>
    
    template<typename... T>
    void test(const T &&...);
    
    template <class... Args>
    auto foo(Args&&... args)
    -> decltype(test(std::forward<Args>(args)...), void())
    {}
    
    struct X {};
    
    int main() {
        X x1, x2;
        //foo(x1, x2);
        foo(std::move(x1), std::move(x2));
        foo(X{}, X{});
    }
    

    如果切换注释,示例将无法按要求编译。

        3
  •  1
  •   Justin    7 年前

    真正地 const&& ; 它们不会成为通用参考:

    template <class... Args>
    auto foo(const Args&&... args) -> void { ... };
    

    只能绑定到右值,而不能绑定到左值。

    #include <iostream>
    
    template <class... Args>
    auto foo(const Args&&... args) -> void
    {
        std::cout << __PRETTY_FUNCTION__ << '\n';
    }
    
    struct X {};
    
    int main () {
        X x1, x2;
        // foo(x1, x2);                    // compile error
        foo(std::move(x1), std::move(x2)); // accepted [OK]
        // foo(std::move(x1), x2);         // compile error
        foo(X{}, X{});                     // accepted [OK]
    }
    

    On Coliru

    const