Можно ли использовать параметр по ссылке const, запрещая конверсии, чтобы вместо этого не выполнялись временные ряды?

Иногда нам нравится брать большой параметр по ссылке, а также сделать ссылку const, если возможно, объявить, что это входной параметр. Но, создав ссылку const, компилятор затем позволяет себе преобразовывать данные, если они неправильного типа. Это означает, что это не так эффективно, но более тревожным является тот факт, что я думаю, что имею в виду исходные данные; возможно, я возьму его адрес, не понимая, что я, по сути, беру адрес временного.

Вызов bar в этом коде невозможен. Это желательно, потому что ссылка не соответствует правильному типу. Вызов bar_const также неправильного типа, но он молча компилируется. Это нежелательно для меня.

#include<vector>
using namespace std;

int vi;

void foo(int &) { }
void bar(long &) { }
void bar_const(const long &) { }

int main() {
   foo(vi);
   // bar(vi); // compiler error, as expected/desired
   bar_const(vi);
}

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

(Очевидно, что int и long - очень маленькие типы, но я был пойман с более крупными структурами, которые могут быть преобразованы друг в друга. Я не хочу, чтобы это молчаливо случалось, когда я принимаю const, иногда отмечая конструкторы как явные, но это не идеально)

Обновление: Я предполагаю, что система похожа на следующую: представьте, что у вас есть две функции X byVal(); и X& byRef(); и следующий блок кода:

 X x;
 const_lvalue_ref<X> a = x; // I want this to compile
 const_lvalue_ref<X> b = byVal(); // I want this to fail at compile time
 const_lvalue_ref<X> c = byRef(); // I want this to compile

Этот пример основан на локальных переменных, но я хочу, чтобы он также работал с параметрами. Я хочу получить какое-то сообщение об ошибке, если я случайно передаю ref-to-tempor или ref-to-copy, когда думаю, что я передам что-то легкое, например ref-to-lvalue. Это всего лишь "стандарт кодирования" - если я действительно хочу разрешить передачу ссылки на временную, я буду использовать простой const X&. (Я нахожу этот фрагмент на Boost FOREACH, чтобы быть весьма полезным.)

Ответ 1

(Отвечая на мой собственный вопрос благодаря этому замечательному ответу по другому вопросу, который я задал. Спасибо @hvd.)

Короче говоря, обозначение параметра функции как volatile означает, что он не может быть привязан к значению r. (Может ли кто-нибудь приклеить стандартную цитату для этого? Временные пользователи могут быть привязаны к const&, но не к const volatile &, по-видимому. Это то, что я получаю от g++ - 4.6.1. (Дополнительно: см. этот расширенный поток комментариев для некоторых деталей gory, которые проходят над моей головой:-)))

void foo( const volatile Input & input, Output & output) {
}

foo(input, output); // compiles. good
foo(get_input_as_value(), output); // compile failure, as desired.

Но, вы действительно не хотите, чтобы параметры были volatile. Поэтому я написал небольшую обертку для const_cast volatile. Таким образом, подпись foo становится такой:

void foo( const_lvalue<Input> input, Output & output) {
}

где оболочка:

template<typename T>
struct const_lvalue {
    const T * t;
    const_lvalue(const volatile T & t_) : t(const_cast<const T*>(&t_)) {}
    const T* operator-> () const { return t; }
};

Это может быть создано только из lvalue

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

Я надеюсь, что по привычке сделаю это со всеми подходящими параметрами по умолчанию.

Демо на идеон

Ответ 2

Ну, если ваш "большой параметр" - это класс, первое, что нужно сделать, это убедиться, что вы выделяете любые конструкторы отдельных выражений (кроме конструктора копирования):

class BigType
{
public:
    explicit BigType(int);
};

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

Тогда он не будет автоматически преобразован, поскольку для компилятора не используются неявные конструкторы для преобразования. У вас, вероятно, нет глобальных операторов преобразования, которые делают этот тип, но если вы это сделаете, то

Если это не сработает для вас, вы можете использовать магию шаблонов, например:

template <typename T>
void func(const T &); // causes an undefined reference at link time.

template <>
void func(const BigType &v)
{
    // use v.
}

Ответ 3

Если вы можете использовать С++ 11 (или их части), это легко:

void f(BigObject const& bo){
  // ...
}

void f(BigObject&&) = delete; // or just undefined

Пример Live на Ideone.

Это будет работать, потому что привязка к rvalue ref предпочтительнее связывания с ссылкой на const для временного объекта.

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

struct BigObjWrapper{
  BigObjWrapper(BigObject const& o)
    : object(o) {}

  BigObject const& object;
};

void f(BigObjWrapper wrap){
  BigObject const& bo = wrap.object;
  // ...
}

Живой пример в Ideone.

Ответ 4

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

void bar_const(const long *) { }

Таким образом, пользователь должен передать указатель. И вы не можете получить указатель на временный (если только пользователь не злонамерен).

Это, как говорится, я думаю, что ваше мышление по этому вопросу... неверно. Это доходит до этого момента.

Возможно, я возьму его адрес, не понимая, что я, по сути, беру адрес временного.

Принимая адрес a const&, который является временным, на самом деле прекрасен. Проблема в том, что вы не можете хранить его надолго. Вы также не можете передать право собственности на него. В конце концов, вы получили ссылку const.

И эта часть проблемы. Если вы берете const&, ваш интерфейс говорит: "Мне разрешено использовать этот объект, но я его не владею, и я не могу передать кому-то другому собственность". Поскольку вы не являетесь владельцем объекта, вы не можете хранить его в течение длительного времени. Это означает const&.

Взятие a const* может быть проблематичным. Зачем? Потому что вы не знаете, откуда пришел этот указатель. Кому принадлежит этот указатель? const& имеет ряд синтаксических гарантий, которые мешают вам совершать плохие вещи (пока вы не берете его адрес). const* ничего не имеет; вы можете скопировать этот указатель на ваш сердечный контент. В вашем интерфейсе ничего не говорится о том, разрешено ли вам владеть объектом или передавать права собственности другим.

Эта двусмысленность - вот почему С++ 11 имеет интеллектуальные указатели, такие как unique_ptr и shared_ptr. Эти указатели могут описывать реальные отношения владения памятью.

Если ваша функция принимает значение unique_ptr по значению, то теперь вы являетесь владельцем этого объекта. Если он принимает shared_ptr, то теперь вы передаете право собственности на этот объект. Существуют синтаксические гарантии, обеспечивающие право собственности (опять же, если вы не предпринимаете неприятные шаги).

В случае, если вы не используете С++ 11, вы должны использовать интеллектуальные указатели Boost для достижения аналогичных эффектов.

Ответ 5

Вы не можете, и даже если бы могли, это, вероятно, не помогло бы. Рассмотрим:

void another(long const& l)
{
    bar_const(l);
}

Даже если вы можете каким-то образом предотвратить привязку к временному вводу bar_const, такие функции, как another, можно вызвать с помощью ссылки связанный с временным, и вы попадете в ту же ситуацию.

Если вы не можете принять временное, вам нужно будет использовать ссылку на не-const или указатель:

void bar_const(long const* l);

требуется инициализировать lvalue. Конечно, такая функция, как

void another(long const& l)
{
    bar_const(&l);
}

все равно вызовет проблемы. Но если вы в глобальном используйте указатель, если срок жизни объекта должен выходить за пределы конца вызова, то, надеюсь, автор another подумает о том, почему он принимает адрес и избегать его.

Ответ 6

Я думаю, что ваш пример с int и long является немного красной селедкой, так как в каноническом С++ вы никогда не будете передавать встроенные типы по ссылке const: вы передаете их по значению или по неконстантной ссылке.

Итак, допустим, что у вас есть большой пользовательский класс. В этом случае, если он создает для вас временные ресурсы, это означает, что вы создали неявные преобразования для этого класса. Все, что вам нужно сделать, это отметить все конструкторы преобразования (те, которые могут быть вызваны с одним параметром) как explicit, и компилятор не позволит автоматически создавать эти временные ряды. Например:

class Foo
{
    explicit Foo(int bar) { }
};