Семантика для обернутых объектов: ссылка / значение по умолчанию через std::move/std::ref

В последнее время я часто использую естественную идиому, которую я "открыл" в C++11, - это то, что обернутый объект может автоматически содержать ссылку, когда это возможно. Основной вопрос здесь будет о сравнении поведения этой "идиомы" с другими поведениями в стандарте (см. Ниже).

Например:

template<class T>
struct wrap{
    T t;
};
template<class T> wrap<T> make_wrap(T&& t){
    return wrap{std::forward<T>(t)};
}

Таким образом, для кода

double a = 3.14
double const c = 3.14

Я получил,

typeid( make_wrap(3.14) ) --> wrap<double>
typeid( make_wrap(a) ) --> wrap<double&>
typeid( make_wrap(c) ) --> wrap<double const&>

с которым, если я буду осторожен (с висящими ссылками), я справлюсь довольно хорошо. И если я хочу избежать ссылок, я делаю:

typeid( make_wrap(std::move(a)) ) --> wrap<double> // noref
typeid( make_wrap(std::move(c)) ) --> wrap<double const> // noref

Таким образом, это поведение кажется естественным в C++11.

Затем я вернулся к std::pair а также std::make_pair и как-то я ожидал, что они использовали это новое, казалось бы, естественное поведение, но, очевидно, поведение "более традиционное". Так, например:

typeid( std::make_pair(3.14, 3.14) ) --> std::pair<double, double>
typeid( std::make_pair(a, a) ) --> std::pair<double, double> // noref
typeid( std::make_pair(c, c) ) --> std::pair<double, double> // noref

и для справок:

typeid( std::make_pair(std::ref(a), std::ref(a) ) ) --> std::pair<double&, double&> // ref
typeid( std::make_pair(std::ref(c), std::ref(c) ) ) --> std::pair<double const&, double const&> // const ref

Это задокументировано здесь: http://en.cppreference.com/w/cpp/utility/pair/make_pair

Как вы видите, два поведения "противоположны", в некотором смысле std::ref является дополнением к std::move, Таким образом, оба поведения одинаково гибки в конце, но мне кажется, что std::make_pair поведение сложнее реализовать и поддерживать.

Вопрос в том, является ли текущее поведение std::make_pair отменить ссылки по умолчанию просто проблема обратной совместимости? потому что какое-то историческое ожидание? или есть более глубокая причина, которая все еще существует в C++11?

Как это выглядит так std::make_pair поведение гораздо сложнее реализовать, так как требует специализации для std::ref (std::reference_wrapper) а также std::decay и даже кажется неестественным (при наличии "C++11 move"). В то же время, даже если я решу продолжать использовать первое поведение, я боюсь, что поведение будет довольно неожиданным по отношению к текущим стандартам, даже в C++11.

На самом деле, я очень люблю первое поведение, до такой степени, что элегантное решение может изменить префикс make_something за что-то вроде construct_something чтобы отметить разницу в поведении. (РЕДАКТИРОВАТЬ: один из комментариев предложил посмотреть на std::forward_as_tuple так что другое название соглашения может быть forward_as_something). Что касается именования, ситуация не является четкой, когда передача по значению, передача по ссылке смешивается при построении объекта.


РЕДАКТИРОВАТЬ 2: Это редактирование просто для того, чтобы ответить @Yakk о возможности "копировать" объект переноса с различными свойствами ref/value. Это не часть вопроса, а всего лишь экспериментальный код:

template<class T>
struct wrap{
    T t;
    // "generalized constructor"? // I could be overlooking something important here
    template<class T1 = T> wrap(wrap<T1> const& w) : t(std::move(w.t)){}
    wrap(T&& t_) : t(std::move(t)){} // unfortunately I now have to define an explicit constructor
};

Это, кажется, позволяет мне копировать между несвязанными типами wrap<T&> а также wrap<T>:

auto mw = make_wrap(a);
wrap<double const&> copy0 =mw;
wrap<double&> copy1 = mw; //would be an error if `a` is `const double`, ok
wrap<double> copy2 = mw;

РЕДАКТИРОВАТЬ 3: Это редактирование должно добавить конкретный пример, в котором традиционный вывод ссылки может потерпеть неудачу, зависит от "протокола". Пример основан на использовании Boost.Fusion.

Я обнаружил, насколько неявное преобразование из ссылки в значение может зависеть от соглашения. Например, старый добрый Boost.Fusion следует соглашению STL

Функции генерации Fusion (например, make_list) по умолчанию сохраняют типы элементов как простые нереферентные типы.

Однако это зависит от точного "типа", который помечает ссылку, в случае Fusion был boost::ref и в случае make_pair является... std::ref совершенно не связанный класс. Итак, в настоящее время, учитывая

double a;

тип boost::fusion::make_vector(5., a ) является boost::fusion::vector2<double, double>, Хорошо.

И тип boost::fusion::make_vector(5., boost::ref(a) ) ) is повышение:: фьюжн::vector2`. Хорошо, как задокументировано.

Однако, удивительно, поскольку Boost.Fusion не был написан с учетом C++11 STL, мы получаем: boost::fusion::make_vector(5., std::ref(a) ) ) имеет тип boost::fusion::vector2<double, std::reference_wrapper<double const> >, Сюрприз!

Этот раздел должен был показать, что текущее поведение STL зависит от протокола (например, какой класс использовать для обозначения ссылок), в то время как другой (то, что я назвал "естественным" поведением) использует std::move (или, точнее, приведение к значению) не зависит от протокола, но он более родной для (текущего) языка.

0 ответов

Другие вопросы по тегам