Почему не `const int ci = 2; std::forward‹int›(ci);` работает и как это исправить/обойти?

Простой вопрос: почему следующее не работает (подразумевая копию ci)?

#include <utility>

int main(){
  const int ci = 2;
  std::forward<int>(ci);
}

prog.cpp: в функции 'int main()':
prog.cpp:6:23: ошибка: нет соответствующей функции для вызова 'forward(const int&)'

Проблема проявилась при написании некоторых шаблонных вещей, где у меня есть простой тип держателя следующим образом. Чтобы избежать ненужных копий, я использую идеальную пересылку, где это возможно, но, похоже, это и есть корень проблемы.

template<class T>
struct holder{
    T value;

    holder(T&& val)
        : value(std::forward<T>(val))
    {}
};

template<class T>
holder<T> hold(T&& val){
    // T will be deduced as int, because literal `5` is a prvalue
    // which can be bound to `int&&`
    return holder<T>(std::forward<T>(val));
}

template<class T>
void foo(holder<T> const& h)
{
    std::tuple<T> t;  // contrived, actual function takes more parameters
    std::get<0>(t) = std::forward<T>(h.value); // h.value is `const T`
}

int main(){
    foo(hold(5));
}

Если потребуется какая-либо дополнительная информация, сообщите мне об этом.
Мы будем очень признательны за любую идею, позволяющую обойти эту проблему.


person Xeo    schedule 27.11.2011    source источник
comment
Вы должны сделать это: template <class T> struct holder { holder(T val) : value(std::move(val)){} }; template <class T> holder<typename std::remove_reference<T>::type> hold(T val) { return holder<T>(std::move(val)); } (Да, я знаю, что это выглядит беспорядочно. :))   -  person GManNickG    schedule 27.11.2011
comment
@GMan: Но я не хочу удалять ссылку. :(   -  person Xeo    schedule 27.11.2011
comment
Затем вы называете это как hold(std::ref(x));. :П   -  person GManNickG    schedule 27.11.2011
comment
@GMan: Фу. ;/ Кажется, мне нужно объяснить, что я пытаюсь сделать. В настоящее время я пытаюсь реализовать позиционные параметры, такие как void foo(param_pack<int,int> p){...} /*...*/ foo((_2 = some_var, _1 = 1));. Надуманный пример, но он должен передать мои намерения. К сожалению, я сталкиваюсь с проблемой после проблемы с этим. :( Теперь это ссылки, которые ломаются при попытке их переместить: ideone.com/Vx0Jp   -  person Xeo    schedule 27.11.2011
comment
О, подожди. У меня только что появилась прекрасная идея обойти эту проблему~ Частичные специализации ftw.   -  person Xeo    schedule 27.11.2011
comment
Я собирался предложить это, но в последнее время я старался избегать специализаций, если это возможно. (Но не религиозно, так что я бы сказал, дерзайте.)   -  person GManNickG    schedule 27.11.2011
comment
@GMan: Почему вы пытаетесь избежать специализаций? И особая причина? :)   -  person Xeo    schedule 27.11.2011
comment
Я только что обнаружил в прошлом, когда я пишу общий код, и я говорю, что он не работает с этим типом... специализируйтесь! в конечном итоге это кусает меня, потому что проблема была не в конкретном типе type, а во взаимодействии со всем набором типов, удовлетворяющих некоторой концепции. Поэтому, если у меня возникла проблема, подобная вашей, было бы лучше зафиксировать концепцию, которая является проблемой, а не конкретный тип (или класс типов). Но иногда (возможно, часто) это просто базовый тип, который нуждается в особом обращении.   -  person GManNickG    schedule 27.11.2011


Ответы (1)


Этот:

#include <utility>

int main(){
  const int ci = 2;
  std::forward<int>(ci);
}

не работает, потому что вы не можете неявно отбросить const. std::forward<T>(u) следует читать как:

Переслать u как T.

Вы пытаетесь сказать:

Forward an lvalue `const int` as an rvalue `int`.

который выбрасывает const. Чтобы не выбрасывать const, вы можете:

#include <utility>

int main(){
  const int ci = 2;
  std::forward<const int>(ci);
}

в котором говорится:

Forward an lvalue `const int` as an rvalue `const int`.

В вашем коде:

template<class T>
void foo(holder<T> const& h)
{
    std::tuple<T> t;  // contrived, actual function takes more parameters
    std::get<0>(t) = std::forward<T>(h.value); // h.value is `const T`
}

квалификатор const в h влияет на выражение выбора члена данных h.value. h.value — это const lvalue int. Вы можете использовать forward, чтобы изменить это значение на const rvalue int, или вы можете использовать forward, чтобы передать его без изменений (как const lvalue int). Вы можете даже использовать forward, чтобы добавить volatile (хотя я не могу придумать веской причины для этого).

В вашем примере я вообще не вижу причин использовать forward (если только вы не уберете const из h).

    std::get<0>(t) = h.value; // h.value is `const T`

Ваш комментарий даже по-прежнему правильный.

Это сухое чтение, но N2951 показывает, что вы можете и не могу сделать с forward и почему. Это было изменено N3143 непосредственно перед стандартизацией, но варианты использования и обоснование по-прежнему действительны и неизменны в окончательной формулировке N3143.

Что можно делать с forward:

  • Вы можете перенаправить lvalue как lvalue.
  • Вы можете перенаправить lvalue как rvalue.
  • Вы можете пересылать rvalue как rvalue.
  • Вы можете перенаправлять менее квалифицированные выражения в более квалифицированные выражения cv.
  • Вы можете пересылать выражения производного типа в доступный однозначный базовый тип.

Что вы нельзя делать с forward:

  • Вы не можете пересылать rvalue как lvalue.
  • Вы не можете пересылать больше выражений с квалификацией cv в выражения с квалификацией с меньшим количеством cv.
  • Вы не можете пересылать произвольные преобразования типов (например, пересылать int как double).
person Howard Hinnant    schedule 27.11.2011
comment
Спасибо за этот обширный ответ. Есть ли способ сохранить возможность пересылки значения? Как я уже сказал в своем комментарии к самому вопросу, я пытаюсь реализовать позиционные параметры, и я хотел бы перенаправить все параметры, как если бы они были напрямую переданы в функцию. Вот полный код, который у меня сейчас есть (~300 LoC), хотя GCC, похоже, не нравится мой Обман SFINAE на switch_. - person Xeo; 27.11.2011
comment
Я уже обнаружил несколько ограничений в своей системе, например, я не могу связать временные объекты типа T с T const& или T&& в param_pack, не получая оборванных ссылок. Есть ли способ исправить/улучшить это (если вы готовы продираться через код)? - person Xeo; 27.11.2011
comment
@Xeo: я не уверен. Но мне удалось исправить ваш код, чтобы он работал на clang/libc++. std::move(pos::template switch_<0>(arg1, arg2, arg3).value (добавьте шаблон 3 места). Сделать конструктор param_pack BoundList менее универсальным: template<class B1, class B2, class B3> param_pack(bound_list<B1, B2, B3>&& bl). С этими изменениями ваш код запускается для меня и выводит: Value ctor Move ctor Move ctor Move ctor Copy ctor Move ctor Move ctor hi 3 1 Dtor Dtor - person Howard Hinnant; 27.11.2011
comment
О, ужасный спецификатор вложенных шаблонов! Спасибо за исправление. На самом деле код, который я связал, был не самым современным, но я заметил это слишком поздно. Я уже внес изменения в ctor, так как bound_list все равно будет временным. У меня на VS11 вывод такой же, но я хотел бы получить эту копию. К сожалению, step-in на самом деле не помогает для VS11, поскольку соответствующая функция, в которой это происходит, глубоко скрыта внутри макроса для имитации вариативных шаблонов. :( Для тех, кто читает это и кому интересно, здесь приведен фиксированный код для GCC. - person Xeo; 28.11.2011