Как написать const ref, не разрешая временное создание

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

то есть я хочу, чтобы с помощью const-ref-but-no-temporaries-created. [отредактировал эту фразу, чтобы уточнить ее!]

Я не могу придумать никакого способа выразить это на С++ напрямую:

1 void fn(const T & t)
создаст временный T из S, если существует (неявный) T(S).

2 void fn(T & t)
будет разрешать только неконстантные t в качестве аргумента (мне нужно, чтобы это работало и для константных t).

3 void fn(T && t)
требует неконстантного rvalue.

4 void fn(const T && t)
требуется const rvalue

Есть ли способ сделать это - принять любой аргумент по ссылке, const, не позволяя создавать временный файл?


Неудачные мысли:

Предоставление # 2 и # 4 без предоставления двух других, и теперь у нас есть однозначный fn, который берет ссылку на любой существующий T (но не будет автоматически генерировать временный)?

Нет, это тоже не подходит, потому что мне также нужно иметь возможность привязываться к const T & - где целевой объект сам передается константной ссылкой на наш вызывающий объект...


Думаю, я не вижу способа добиться этого, за исключением случая, когда я могу контролировать T, чтобы убедиться, что он не предоставляет неявный T(S).

Я прав, или я пропускаю какое-то решение?


person Mordachai    schedule 30.03.2017    source источник
comment
void fn(const T & t) + void fn(const T && t) = delete должно помочь. В качестве альтернативы поместите туда static_assert, чтобы объяснить людям, почему они должны давать временное имя, которое они хотят передать.   -  person nwp    schedule 30.03.2017
comment
Почему вы все равно хотите запретить временные?   -  person Christian Hackl    schedule 30.03.2017
comment
Что вы хотите делать в своей функции?   -  person Sniper    schedule 30.03.2017
comment
Наша кодовая база широко использует CString MFC, который имеет CString(const char-type *) неявные ctors. Это делает тривиальным случайное наличие функций, которые принимают const CString &, фактически создают временную CString на месте - что может быть безумно низкой производительностью (когда вы это заметите) или просто перетаскиванием, когда вы этого не делаете. Поэтому мне нужны различные бесплатные fn, которые берут CString, если они уже являются CString, но, пожалуйста, не создавайте временные файлы, если они не являются (генерировать ошибку!)   -  person Mordachai    schedule 30.03.2017
comment
FWIW, на самом деле это бесплатные функции в моем реальном случае использования.   -  person Mordachai    schedule 30.03.2017
comment
Ниже вы написали: я только пытаюсь избежать того, чтобы fn(t) молча преобразовывал не-t в t. Это полностью отличается от вашего фактического вопроса, который касается значений rvalue и lvalue (все типа T). Возможно, вам нужно сделать некоторые уточнения.   -  person Nir Friedman    schedule 30.03.2017
comment
Вам следует подумать о замене const CString & чем-то вроде std::string_view.   -  person nwp    schedule 30.03.2017
comment
Извините за запутанный вопрос - это было мое основное замешательство (и, следовательно, нужно спросить! ;)   -  person Mordachai    schedule 31.03.2017


Ответы (2)


Судя по комментариям, кажется, что ваш фактический вопрос не имеет ничего общего с rvalue и lvalue, а просто предотвращает неявные преобразования. Вы можете сделать это, изменив функцию на шаблон функции, а затем ограничив шаблон с помощью sfinae:

template <class T, std::enable_if_t<std::is_same<std::decay_t<T>, CString>::value, int> = 0>
void fn(const T&) {

}

Это будет работать, только если вызываться с каким-то CString, тип, который неявно преобразуется в CString, не будет работать.

Живой пример: http://coliru.stacked-crooked.com/a/80602c39cdc4d35e

Тем не менее, лучшим решением было бы просто изменить эти функции, чтобы они принимали string_view, которые можно дешево неявно построить из различных типов строк. Я не знаю, обрабатывает ли он CString или нет, но вы всегда можете исправить свой собственный. Это довольно простой класс для написания. http://en.cppreference.com/w/cpp/string/basic_string_view

person Nir Friedman    schedule 30.03.2017
comment
Действительно - я искал ответ, не зная, где искать. SFINAE звучит как многообещающий подход. - person Mordachai; 30.03.2017
comment
Я ждал, кажется, столетия хорошего string_view и диапазонов, но у меня не было времени, чтобы увидеть, действительно ли они достаточно хороши для моего использования? Обязательно посмотрю (да и адаптеры для CStrings написать не проблема) - person Mordachai; 30.03.2017
comment
@Mordachai Могут быть некоторые неприятности, потому что вы в основном теряете любые функции-члены CString, если это похоже на std::string, то это много. Но обычно вы можете сделать что-то подобное со стандартными алгоритмами или циклом for. Помимо этого, хотя это довольно тривиально; строковое представление — это всего лишь два указателя, один в начало, один в конец. Некоторые константно-правильные начальные и конечные члены. И несколько неявных конструкторов. Вы можете написать что-то достаточно хорошее для вашего использования примерно в 100 строках кода. - person Nir Friedman; 30.03.2017
comment
спасибо за информацию - да, у меня всегда была идея строкового представления - но я отчаянно хочу, чтобы std::range стал вещью, чтобы ее можно было сделать правильно, а не как хакерскую вещь. Вся стандартная библиотека должна была быть написана с представлениями (диапазонами) с самого начала — а потом уже богатые диапазон-адаптеры/представления-адаптеры. Как бы то ни было, это довольно глупо (но становится лучше). В моем случае здесь - даже представление глупее, чем то, что я хочу - это компилятор - когда вы видите get_length(CString), это наиболее эффективный способ решить эту проблему - person Mordachai; 30.03.2017
comment
@Mordachai Чтобы было ясно, вам не нужны диапазоны, чтобы иметь хорошее решение для этого. string_view будет стандартизирован без диапазонов. Диапазоны в основном просто необходимы для хорошей композиции, а это не то, что здесь происходит. - person Nir Friedman; 30.03.2017

Если это бесплатная функция, вы можете сделать так, как предлагает nwp, и добавить перегрузку, взяв ссылку rvalue и удалив ее.

void fn(const SomeType& t)
{
    // stuff to do with const lvalue
}

void fn(SomeType&&) = delete; // compiler error if you give me an rvalue

Это работает, потому что ссылка на rvalue предпочтительнее ссылки на const. Поэтому, когда срабатывает разрешение перегрузки и вы передаете временное значение, компилятор выберет void fn(const T&&) как наилучшее совпадение. После этого он увидит, что функция удалена, и выдаст ошибку компилятора.

person NathanOliver    schedule 30.03.2017
comment
Фу! Я хочу, чтобы это работало для всех моих реальных случаев использования! Так близко! Но в моем случае я также хочу разрешить это: fn(fm()) - где fm() возвращает T - но в данном случае это const rvalue, но выбор для создания const rvalue был явно разумным в коде. Я только пытаюсь избежать fn(t) молчаливого преобразования не-t в t... :( - person Mordachai; 30.03.2017
comment
@Mordachai Возможно, этот ответ, который я только что написал по связанному вопросу, может помочь. Я не уверен, хотя о том, как именно связать их вместе. Мне нужно подумать об этом еще немного. - person NathanOliver; 30.03.2017
comment
@Mordachai У меня тоже была ошибка в коде. Это должно было быть void fn(SomeType&&) = delete;, а не void fn(const SomeType&&) = delete;. - person NathanOliver; 30.03.2017
comment
Даже исправленная неконстантная версия вашего решения не работает для меня из-за того, что мне нужно разрешить f(g()), где g() возвращает T (на языке этого Q). Следовательно, в этом случае значение r вполне разумно - оно не индуцируется - оно уже существует по определению, явно. Мне интересно, есть ли какая-то магия шаблонов, чтобы отличить такой случай - все не-4_ от настоящих T... может быть, это плодотворная мысль.... - person Mordachai; 30.03.2017