Есть ли reference_wrapper‹› для ссылок rvalue?

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

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.


person Johannes Schaub - litb    schedule 26.02.2011    source источник
comment
См. также: stackoverflow.com/questions/4871273/   -  person GManNickG    schedule 26.02.2011


Ответы (4)


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

20.8.10.1.2/10 in N3225

Значения связанных аргументов v1, v2, ..., vN и соответствующих им типов V1, V2, ..., VN зависят от типов TiD, полученных из вызова bind, и 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), поскольку я ее не вижу.

person AProgrammer    schedule 26.02.2011
comment
спасибо, реализовал идею с is_bind_expression. Я думаю, что он достаточно общий, чтобы быть полезным, поэтому я приму ваш ответ! - person Johannes Schaub - litb; 26.02.2011
comment
Спасибо большое. Это решило мои проблемы с функцией потока С++ 0x, принимающей ссылку lvalue :) (g++ 4.6) - person sehe; 27.09.2011

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

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

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

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

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

person sellibitze    schedule 26.02.2011
comment
Это веская причина для того, чтобы он не был доступен более напрямую. - person AProgrammer; 26.02.2011

Я искал «reference_wrapper для значений r», когда наткнулся на этот вопрос. Не уверен, что мой ответ будет полезен, он не связан с 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");

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

person Taras    schedule 27.04.2018
comment
Это законный способ? std::addressof(const T&&) = delete;. - person Mister_Jesus; 18.02.2019
comment
@MrBin Извините, не уверен, что понял вопрос. Легальный способ сделать что? - person Taras; 20.02.2019
comment
: _pointer(::std::addressof(ref_value)) — ref_value может быть типа lvalue или rvalue. Для rvalue std::addressof удалено. - person Mister_Jesus; 20.02.2019
comment
@MrBin О. Да, это допустимо, потому что ссылка rvalue — если она привязана к именованному идентификатору (в данном случае ref_value) — сама по себе является lvalue. Таким образом, его адрес может быть взят. Кстати, именно по этой причине мы должны использовать std::move (снова!) для аргументов, полученных по ссылке rvalue, и почему мы должны использовать std::forward с пересылающими (универсальными) ссылками: чтобы восстановить rvalue'ность этот объект. - person Taras; 22.02.2019
comment
Я забыл это правило для named rvalue. Спасибо. - person Mister_Jesus; 22.02.2019

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

auto func = [=]() mutable {
    f(std::move(s));
};
person Puppy    schedule 26.02.2011
comment
Верно. Но из-за отсутствия захвата хода строка будет захвачена копированием. Вы даже можете сделать это лучше, используя возможности захвата движений bind - см. Мой ответ. - person sellibitze; 26.02.2011