стиль в привязке ссылки на объект или пустышку - PullRequest
5 голосов
/ 09 октября 2010

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

A &&var_or_dummy = modify? static_cast<A&&>( my_A )
                         : static_cast<A&&>( static_cast<A>( my_A ) );

(Этот код не работает в моем недавнем GCC 4.6… Я его помнюработал раньше, но теперь он всегда возвращает копию.)

В первой строке static_cast преобразует my_A из lvalue в xvalue.(C ++ 0x §5.2.9 / 1-3) Внутренний static_cast во второй строке выполняет преобразование lvalue в rvalue, а внешний получает значение xvalue из этого prvalue.

Это появляетсябыть поддержанным, потому что именованная ссылка условно связана с временной согласно §12.2 / 5.Тот же трюк работает в C ++ 03 со ссылкой const.

Я также могу написать то же самое менее подробно:

A &&var_or_dummy = modify? std::move( my_A )
                         : static_cast<A&&>( A( my_A ) );

Теперь это намного короче.Первая аббревиатура сомнительна: move должен сигнализировать о том, что с объектом что-то происходит, а не просто перестановка lvalue-to-xvalue-to-lvalue.Заблуждение, что move нельзя использовать после :, потому что вызов функции прервал бы временную привязку к ссылке.Синтаксис A(my_A), возможно, более понятен, чем static_cast, но технически он эквивалентен приведению в стиле C.

Я также могу пройти весь путь и написать его полностью в приведениях в стиле C:

A &&var_or_dummy = modify? (A&&)( my_A ) : (A&&)( A( my_A ) );

В конце концов, если это будет идиома, это должно быть удобно, и static_cast на самом деле не защищает меня от чего бы то ни было - реальная опасность заключается в том, что нельзя напрямую связываться с my_Aв случае true.

С другой стороны, это легко определяется тем, что имя типа повторяется три раза.Если бы A был заменен большим, некрасивым идентификатором шаблона, я бы действительно хотел настоящий ярлык.

(Обратите внимание, что V оценивается только один раз, несмотря на то, что он появляется пять раз:)

#define VAR_OR_DUMMY( C, V ) ( (C)? \
  static_cast< typename std::remove_reference< decltype(V) >::type && >( V ) \
: static_cast< typename std::remove_reference< decltype(V) >::type && >   (  \
  static_cast< typename std::remove_reference< decltype(V) >::type >( V ) ) )

Хаки, как и макросы, я думаю, что это лучшая альтернатива.Это немного опасно, потому что возвращает xvalue, поэтому его не следует использовать вне инициализации ссылки.

Должно быть что-то, о чем я не подумал ... предложения?

Ответы [ 3 ]

2 голосов
/ 09 октября 2010

Я вижу две проблемы с вашим подходом.

Вы полагаетесь на поведение

int   i = 0;
int&  j = true?      i  :      i;
int&& k = true? move(i) : move(i);
assert(&i == &j); // OK, Guaranteed since C++98
assert(&i == &k); // Does this hold as well?

Текущий стандартный черновик N3126 содержит 5.16 / 4:

Если второй и третий операнды [к условному оператору] являются glvalues ​​одной и той же категории значений и имеют одинаковый тип, результат будет иметь этот тип и категорию значений

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

Кроме того, вы полагаетесь на то, что компилятор продлит время жизни временного объекта y относится к следующему примеру:

A func();

A&& x = func();                   // #1
A&& y = static_cast<A&&>(func()); // #2

x не будет висячей ссылкой , но я не совсем уверен насчет y. Я думаю, что правило о продлении времени жизни временных файлов должно применяться только в тех случаях, когда выражения инициализатора имеют pure rvalue. По крайней мере, это значительно упростит реализацию. Кроме того, GCC, похоже, согласен со мной по этому вопросу. GCC не продлевает время жизни временного объекта A во втором случае. Это было бы проблемой с привязкой в ​​вашем подходе .

Обновление: Согласно 12.2 / 5 время жизни временных объектов должно быть увеличено в обоих случаях, # 1 и # 2. Кажется, что ни один из пунктов в списке исключений здесь не применим. Опять же, GCC, кажется, глючит в этом отношении.

Одним из простых решений вашей проблемы было бы:

vector<A> tempcopy;
if (!modify) tempcopy.push_back(myA);
A& ref = modify ? myA : tempcopy.back();

В качестве альтернативы вы можете использовать boost :: scoped_ptr вместо вектора.

2 голосов
/ 09 октября 2010

Просто избегайте всего этого беспорядка с помощью дополнительного вызова функции:

void f(bool modify, A &obj) {
  return [&](A &&obj) {
    real();
    work();
  }(modify ? std::move(obj) : std::move(A(obj)));
}

Вместо:

void f(bool modify, A &obj) {
  A &&var_or_dummy = /* ??? */;
  real();
  work();
}

Это лямбды, лямбды, везде !

0 голосов
/ 10 октября 2010

Вопрос безопасности xvalue можно несколько обойти, предоставив альтернативу для использования внутри выражений.Проблемы совершенно другие, теперь мы не хотим получить результат xvalue и можем использовать функцию:

template< typename T >
T &var_or_dummy( bool modify, T &var, T &&dummy = T() ) {
    if ( modify ) return var;
    else return dummy = var;
}

    maybe_get_result( arg, var_or_dummy( want_it, var ) );

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

Повышение Необязательный может немного помочь;требуется только CopyConstructible T:

template< typename T >
T &var_or_dummy( bool modify, T &var,
                 boost::optional< T > &&dummy = boost::optional< T >() ) {
    if ( modify ) return var;
    else return dummy = var;
}

Необязательно, но полезно, но имеет некоторое совпадение с объединениями C ++ 0x.Это не слишком сложно для переопределения.

template< class T >
struct optional_union {
    bool valid;
    union storage {
        T obj; // union of one non-POD member simply reserves storage

        storage() {} // uh, what could the constructor/destructor possibly do??
        ~storage() {}
    } s;

    optional_union() : valid( false ) {}
    optional_union &operator=( T const &in ) {
        new( &s.obj ) T( in ); // precondition: ! valid
        valid = true;
        return *this; 
    }
    ~optional_union()
        { if ( valid ) s.obj.~T(); }
};

template< typename T >
T &var_or_dummy( bool modify, T &var,
                 optional_union< T > &&dummy = optional_union< T >() ) {
    if ( modify ) return var;
    else return ( dummy = var ).s.obj;
}

Класс optional_union достаточен только для этого приложения ... очевидно, он может быть значительно расширен.

...