Есть ли reference_wrapper <> для ссылок rvalue? - PullRequest
13 голосов
/ 26 февраля 2011

Интересно, как можно сделать следующее

void f(string &&s) { 
  std::string i(move(s)); 
  /* other stuff */ 
} 

int main() { 
  std::string s; 
  bind(f, s)(); // Error.
  bind(f, move(s))(); // Error.
  bind(f, ref(s))(); // Error.
}

Как я могу передать ссылку на rvalue и сохранить ее как ссылку на rvalue (возможно, упакованную) в оболочке вызова? Я знаю, что могу вручную написать класс типа std::reference_wrapper<>, который имеет функцию преобразования в T&&, но я бы предпочел избежать этого и использовать стандартную технологию.


Я реализовал это так, как AProgrammer рекомендует:

template<typename T> struct adv { 
  T t; 
  explicit adv(T &&t):t(forward<T>(t)) {} 
  template<typename ...U> T &&operator()(U &&...) { 
    return forward<T>(t); 
  } 
}; 

template<typename T> adv<T> make_adv(T &&t) { 
  return adv<T>{forward<T>(t)}; 
}

namespace std { 
  template<typename T> 
  struct is_bind_expression< adv<T> > : std::true_type {}; 
} 

Теперь я могу сказать

void f(string &&s) { 
  std::string i(move(s)); 
  /* other stuff */ 
} 

int main() { 
  std::string s; 
  bind(f, make_adv(move(s)))(); // Works!
}

Если мы передадим lvalue в make_adv, он будет пересылать его как lvalue со ссылкой на входной аргумент, так что в этом случае он может быть использован вместо std::ref.

Ответы [ 4 ]

7 голосов
/ 26 февраля 2011

Мой взгляд на это.

20.8.10.1.2 / 10 в N3225

Значения связанных аргументов v1, v2, ..., vN и их соответствующиетипы V1, V2, ..., VN зависят от типов TiD, полученных из вызова для привязки, и cv-квалификаторов cv оболочки вызова g следующим образом:

  • , если TiD является reference_wrapper,Аргументом является tid.get (), а его типом Vi является T &;
  • , если значение is_bind_expression :: value равно true, аргументом является tid (std :: forward (uj) ...) и его типVi - это result_of :: type;
  • , если значение j is_placeholder :: value не равно нулю, аргумент равен std :: forward (uj), а его тип Vi равен Uj &&;
  • в противном случаезначение равно tid, а его тип Vi равен TiD cv &.

Таким образом, единственная возможность иметь ссылку на rvalue - это иметь is_bind_expression<TiD>::value true или is_placeholder<TiD>::value не ноль.Вторая возможность имеет последствия, которые вам не нужны, и достижение желаемого результата с первой будет означать, что проблема, которую мы пытаемся решить, будет решена, если мы ограничимся стандартными предоставляемыми типами.Таким образом, единственная возможность - предоставить свою собственную обертку и специализацию для is_bind_expression<TiD> (что разрешено 20.8.10.1.1 / 1), поскольку я ее не вижу.

6 голосов
/ 26 февраля 2011

Как передать ссылку на rvalue и сохранить ее как ссылку на rvalue в оболочке вызова?

Проблема здесь в том, что такой объект функции связывания может быть вызван несколько раз. Если бы объект функции переадресовал связанный параметр как rvalue, это, очевидно, сработало бы только один раз. Так что это немного проблема безопасности.

Но в некоторых случаях этот вид пересылки - именно то, что вы хотите. Вы можете использовать лямбду в качестве посредника:

bind([](string& s){f(move(s));},move(s));

По сути, я придумал эту комбинацию bind + lambda в качестве обходного пути для отсутствующего «захвата-движения».

2 голосов
/ 28 апреля 2018

Я гуглил для "reference_wrapper for rvalues", когда наткнулся на этот вопрос. Не уверен, что мой ответ будет полезен, он не связан с std :: bind и фактически не работает с ним, но для некоторых других случаев он может кому-нибудь помочь.

Вот моя попытка реализовать rvalue_reference_wrapper:

#pragma once

#include <type_traits>
#include <memory>
#include <utility>

template<class T>
class rvalue_reference_wrapper
{
public:
    static_assert(::std::is_object<T>::value, "rvalue_reference_wrapper<T> requires T to be an object type.");

    using type = T;

    rvalue_reference_wrapper(T& ref_value) = delete;

    rvalue_reference_wrapper(T&& ref_value) noexcept
        : _pointer(::std::addressof(ref_value))
    {
    }

    operator T&&() && noexcept
    {
        return ::std::move(*_pointer);
    }

    T&& get() && noexcept
    {
        return ::std::move(*_pointer);
    }

    template<class... ArgTypes>
    auto operator()(ArgTypes&&... args) &&
        -> decltype(::std::invoke(::std::declval<rvalue_reference_wrapper<T>>().get(), ::std::forward<ArgTypes>(args)...))
    {
        return (::std::invoke(::std::move(*this).get(), ::std::forward<ArgTypes>(args)...));
    }

private:
    T* _pointer;
};

template<class T>
inline rvalue_reference_wrapper<T> rv_ref(T& ref_value) = delete;

template<class T>
inline ::std::enable_if_t<!(::std::is_lvalue_reference<T>::value), rvalue_reference_wrapper<T>> rv_ref(T&& ref_value) noexcept
{
    return rvalue_reference_wrapper<T>(::std::forward<T>(ref_value));
}

#ifdef _MSC_VER
namespace std
{
    template<class T>
    struct _Unrefwrap_helper<rvalue_reference_wrapper<T>>
    {
        using type = T &&;
        static constexpr bool _Is_refwrap = true;
    };
}
#else
#pragma error("TODO : implement...")
#endif

Последняя специализация в области имен std позволяет реализации стандартной библиотеки MSVC для работы с моим типом, например, при использовании std :: make_tuple:

int a = 42;
auto p_int = std::make_unique<int>(42);
auto test_tuple = std::make_tuple(42, std::ref(a), rv_ref(std::move(p_int)));
static_assert(std::is_same<decltype(test_tuple), std::tuple<int, int &, std::unique_ptr<int>&&>>::value, "unexpected result");

Я полагаю, что было бы нетрудно реализовать подобную логику "разворачивания" для других реализаций стандартной библиотеки.

0 голосов
/ 26 февраля 2011

Вы можете использовать изменяемый лямбда-объект.

auto func = [=]() mutable {
    f(std::move(s));
};
...