rvalue или lvalue (const) ссылочный параметр

Я хочу передать параметр(ы) (некоторого конкретного типа, скажем, int) в функцию-член по ссылке r- или l-value (const). Мое решение:

#include <type_traits>
#include <utility>

struct F
{
    using desired_parameter_type = int;

    template< typename X, typename = typename std::enable_if< std::is_same< typename std::decay< X >::type, desired_parameter_type >::value >::type >
    void operator () (X && x) const
    {
        // or even static_assert(std::is_same< typename std::decay< X >::type, desired_parameter_type >::value, "");
        std::forward< X >(x); // something useful
    }
};

Другой пример находится здесь http://pastebin.com/9kgHmsVC.

Но это слишком многословно. Как это сделать проще?

Возможно, мне следует использовать суперпозицию std::remove_reference и std::remove_const вместо std::decay, но здесь просто упрощение.


person Tomilov Anatoliy    schedule 04.05.2013    source источник
comment
Нет смысла передавать простой тип, такой как int, по rvalue. В более общем случае, я считаю, что C++11 имеет для этого особый случай, когда параметр rvalue будет прочитан как параметр rvalue или lvalue. Я не могу вспомнить точную ситуацию, когда это применимо.   -  person Dave    schedule 04.05.2013
comment
Итак, вы хотите, чтобы x было либо ссылкой на rvalue (если передается ссылка на rvalue), либо ссылкой на lvalue на const? Правильно ли я понимаю, что X&& неприемлемо для вас, потому что это будет ссылка lvalue на не-const при передаче lvalue?   -  person Andy Prowl    schedule 04.05.2013
comment
Ну вот, я думаю, вы пытаетесь сделать это: thbecker.net/articles/rvalue_references /section_07.html, что объясняется с помощью rvalue на следующей странице. Пишите, если я неправильно понял вопрос.   -  person Dave    schedule 04.05.2013
comment
@AndyProwl Моя проблема заключается в том, как предоставить параметр некоторого конкретного типа, но с неопределенной ссылкой.   -  person Tomilov Anatoliy    schedule 04.05.2013
comment
@Dukales: я не могу следить за тобой. Если вы просто сделаете template<typename X> void operator () (X&& x), вам будет разрешено передавать любые параметры (lvalues ​​или rvalues). Учитывая аргумент типа U, вывод типа сделает x U&& x в случае, если аргумент является rvalue, и U& x в случае, если аргумент является lvalue   -  person Andy Prowl    schedule 04.05.2013
comment
@AndyProwl, ты правильно понял мои пожелания.   -  person Tomilov Anatoliy    schedule 04.05.2013
comment
@Dukales: я пытался ответить. Если я не упустил что-то очевидное, вам просто нужна ссылка lvalue на const.   -  person Andy Prowl    schedule 04.05.2013


Ответы (3)


Если я правильно понимаю ваш вопрос, вы хотите иметь одну функцию, параметр которой является либо ссылкой на rvalue (в случае предоставления rvalue), либо ссылкой lvalue на const (в случае предоставления lvalue).

Но что будет делать эта функция? Что ж, поскольку он должен быть в состоянии обрабатывать оба случая, включая случай, когда предоставляется lvalue, он не может изменять свои входные данные (по крайней мере, не тот, который привязан к параметру x) — если бы это было так, это нарушило бы семантику const ссылка.

Но опять же, если он не может изменить состояние параметра, нет причин разрешать ссылку rvalue: пусть x будет все время ссылкой lvalue на const. Ссылки lvalue на const могут связываться с rvalue, поэтому вам будет разрешено передавать как rvalue, так и lvalue.

Если семантика функции отличается в зависимости от того, что передается, то я бы сказал, что имеет смысл написать две такие функции: одну, которая принимает ссылку rvalue, и другую, которая принимает ссылку lvalue на const .

person Andy Prowl    schedule 04.05.2013
comment
константность необязательна (поэтому разрешены все &&, &, const &) - person Tomilov Anatoliy; 04.05.2013
comment
@Dukales: я не понимаю :( Что вы имеете в виду? Если функция должна работать с const объектами, это означает, что она не будет пытаться изменить x. Но опять же, это означает, что вам не нужно значение rvalue ссылка - person Andy Prowl; 04.05.2013
comment
Вы знаете о метапрограммировании? Предположим, что функции, которым передаются параметры, будут определять, что делать (как отправлять) с их конкретной ссылкой и константностью. - person Tomilov Anatoliy; 04.05.2013
comment
@Dukales: да, я знаю о метапрограммировании. Но мне было бы любопытно посмотреть, как вы представляете себе работу такой функции. Предположим, вам дан способ написать сигнатуру функции по вашему желанию. Что вы ожидаете написать внутри тела такой функции? - person Andy Prowl; 04.05.2013
comment
Я хочу написать список инициализации внутри конструктора. - person Tomilov Anatoliy; 04.05.2013
comment
В терминах проектирование по контракту я хочу, чтобы все ограничения были локализованы непосредственно в прототипе конструктора. - person Tomilov Anatoliy; 04.05.2013
comment
@Dukales: извините, я не понимаю. Возможно, пример поможет. - person Andy Prowl; 04.05.2013
comment
Я бессилен перед языковым барьером. Это такая боль, чтобы выразить технические идеи. Я лучше подожду. Не вините меня за это. - person Tomilov Anatoliy; 04.05.2013
comment
@Dukales: Просто напишите фрагмент кода, как будет выглядеть тело вашей функции. Если это конструктор, просто напишите класс с этим конструктором и оставьте подпись пустой. Я чувствую, что это трудно выразить из-за какого-то неправильного представления, которое у вас есть, или неправильных предположений, которые вы делаете. Как правило, запись в формальных терминах помогает их обнаружить и прояснить. А может быть, я чего-то не вижу, и тогда конкретный пример помог бы и мне, и вам. Или, если вы предпочитаете подождать, не стесняйтесь :) Может быть, кто-то более проницательный, чем я, ответит - person Andy Prowl; 04.05.2013
comment
@Dukales: Хорошо, а что не так, если вы просто используете T0&&, T1&& и T2&& без каких-либо специальных ограничений SFINAE? - person Andy Prowl; 04.05.2013
comment
В этом случае читабельности наблюдаться не будет. Я хочу наложить ограничения на тип передаваемого значения. - person Tomilov Anatoliy; 04.05.2013
comment
@Dukales: я не понимаю. Какую строку вы хотели бы вести иначе, чем если бы вы просто использовали T0&&, T1&& и T2&&? - person Andy Prowl; 04.05.2013
comment
В этом случае пользователь моей библиотеки не знает об ограничениях типа. - person Tomilov Anatoliy; 04.05.2013
comment
*В примере я забыл указать std::forward< T? >. Я исправил. - person Tomilov Anatoliy; 04.05.2013

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

  • Вы можете перенаправить аргументы в другую функцию. В этом случае использование шаблона не имеет значения, потому что, если задан неправильный тип параметра, он все равно вызовет ошибку компиляции (неправильные типы будут отправлены во вторую функцию). Вы можете использовать template <typename T> blah (T && x).
  • Все остальное потребует от вас написания другого кода в зависимости от того, является ли это ссылкой на значение r или l, поэтому вам в любом случае нужно будет написать 2 функции: blah (const int & x) и blah (int && x).

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

person Dave    schedule 04.05.2013
comment
Под функцией может подразумеваться ctor с массивным списком инициализации. - person Tomilov Anatoliy; 04.05.2013
comment
@Dukales: Но это просто пересылка, так что вы можете использовать шаблоны и по-прежнему получать ошибки компилятора, если он вызывается неправильно. - person Dave; 04.05.2013

На самом деле, это очень хороший вопрос. До сих пор я также использовал трюк с универсальным эталоном плюс молоток enable_if. Здесь я представляю решение, которое не использует шаблоны и использует приведение lvalue в качестве альтернативы.

Далее следует реальный пример, когда ситуация возникает с использованием известного примера использования ofstream на месте, что невозможно (или очень сложно) в C++98 (я использую ostringstream в примере, чтобы сделать его более понятным).

Сначала вы увидите функцию по ссылке lvalue, как это часто бывает в C++98.

#include<iostream>
#include<sstream>
struct A{int impl_;};

std::ostringstream& operator<<(std::ostringstream& oss, A const& a){
    oss << "A(" << a.impl_ << ")"; // possibly much longer code.
    return oss;
}
// naive C++11 rvalue overload without using templates
std::ostringstream& operator<<(std::ostringstream&& oss, A const& a){
    oss << "A(" << a.impl_ << ")"; // ok, but there is code repetition.
    return oss;
}

int main() {

    A a{2};
    {// C++98 way
        std::ostringstream oss;
        oss << a;
        std::cout << oss.str() << std::endl; // prints "A(2)", ok"
    }
    {// possible with C++11, because of the rvalue overload
        std::cout << (std::ostringstream() << a).str() << std::endl; //prints "A(2)", ok
    }
}

Как видите, в C++11 мы можем добиться того, чего не можем в C++98. То есть использовать ostringstream (или ofstream) на месте. Теперь возникает вопрос OP, две перегрузки выглядят очень похожими, можно ли их объединить в одну?

Один из вариантов — использовать универсальную ссылку (Ostream&&) и, при необходимости, enable_if для ограничения типа. Не очень элегантно.

Что я обнаружил, используя этот пример «реального мира», так это то, что если вы хотите использовать один и тот же код для ссылки lvalue и ссылки rvalue, потому что, вероятно, вы можете преобразовать одно в другое!

std::ostringstream& operator<<(std::ostringstream&& oss, A const& a){
    return operator<<(oss, a);
}

Это выглядит как бесконечно рекурсивная функция, но это не так, потому что oss является ссылкой lvalue (да, это ссылка lvalue, потому что у нее есть имя). Таким образом, он вызовет другую перегрузку.

Вам по-прежнему нужно написать две функции, но у одной есть код, который вам не нужно поддерживать.

Таким образом, если «имеет смысл»© применять функцию как к (неконстантной) ссылке lvalue, так и к rvalue, это также означает, что вы можете преобразовать rvalue в lvalue и, следовательно, перенаправить к одной функции. Обратите внимание, что «это имеет смысл» зависит от контекста и значения предполагаемого кода, и это то, что мы должны «сказать» компилятору, явным образом вызвав перегрузку lvalue.

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

Редактируемый код здесь: http://ideone.com/XSxsvY. (обратная связь приветствуется)

person alfC    schedule 04.02.2014