Запретить использование auto для данного класса, обновление C ++ 14 или C ++ 17 - PullRequest
0 голосов
/ 20 октября 2019

Что это за функция, которая позволяет мне использовать auto для не копируемых (и неподвижных) типов в C ++ 17 и не для C ++ 14?

Рассмотрим следующий код:

struct A{
    A(A const&)=delete;
    A(A&&)=delete;
};

int main(){
    auto   a1 = A{}; // ok in C++17, not ok in C++14
    auto&& a2 = A{}; // ok in C++17, ok in C++14
}

Оказывается, это был неверный код в C ++ 14, но он действителен в C ++ 17. Поведение в clang и gcc непротиворечиво: https://godbolt.org/z/af8mEc

Причина, по которой я спрашиваю, состоит в том, что до недавнего времени я делал свои классы (которые представляют ссылки) не копируемыми, помимо прочего, чтобы запретить использование auto, но, к сожалению, оказывается, что теперь техника не работает в C ++ 17.

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

Почему auto a1 = A{}; является допустимым для класса, не подлежащего копированию, в C ++ 17? Является ли какой-то новый случай RVO?

Я думаю, auto семантически нарушен по нескольким спорным причинам, но по крайней мере в C ++ 14 я мог бы предотвратить использование auto (но разрешилиспользование auto&&).

Есть ли еще какой-нибудь способ предотвратить использование auto a = A{}; для определенного класса в C ++ 17 или больше?

Примечание : я задал этот вопрос некоторое время назад Есть ли способ отключить автоматическое объявление для нерегулярных типов? и обнаружил, что решением было отключить копию иПереместите конструкторы в C ++ 14, это имело как концептуальный, так и синтаксический смысл, однако в C ++ 17 это уже не так. *

1 Ответ

1 голос
/ 20 октября 2019

В C ++ 17, если вы предоставляете пользователю значение некоторого типа, пользователь может всегда использовать его для инициализации переменной этого типа.

По существу, определение prvalue изменилось . В C ++ 14 это был объект;в частности, временный объект. Следовательно, оператор типа A a = prvalue_of_type_A; означает перемещение временного объекта в a. Движение может быть исключено, но это логически ход, и поэтому A должен поддерживать конструкцию хода.

В C ++ 17 значение prvalue является не чем иным, как инициализатором дляобъект. Какой объект он инициализирует, зависит от того, как он используется. Использование prvalue для инициализации переменной типа prvalue означает, что вы инициализируете этот объект. Там нет копирования или перемещения. Вы можете посмотреть на это с точки зрения того, что prvalue является безымянным объектом, и A a = prvalue_of_A; просто дает имя этому объекту, а не создает новый объект.

И нет, вы не можете обойтиЭто. Пока вы имеете дело с подлинным значением prvalue некоторого типа, auto a = prvalue; всегда будет определять тип значения prvalue и напрямую инициализировать объект a, без копирования / перемещения.


Другими словами, я думаю, что поведение в C ++ 14 было концептуально правильным.

Хорошо, давайте исследуем это.

Это исследование начинается и заканчивается полным осознанием того, чтоВы не помешали auto a = A{}; работать. Вы запретили конструкцию копирования / перемещения, которая имеет побочный эффект предотвращения этого синтаксиса. Вот почему гарантированная elision сделала ваше «исправление» бессмысленным.

Отказав типу в возможности копирования / перемещения, вы действительно предотвратили этот синтаксис. Но на этом пути было нанесено много побочного ущерба.

Пост, на который вы ссылались, дал обоснование для желания отключить этот синтаксис, так как тип Non-Regular , и этоделает использование auto "хитрым" ... по какой-то причине. Игнорируя, обоснованы ли эти неустановленные причины, ваше решение не просто убрало хитрый синтаксис. Это помешало вам сделать с предметом базовые вещи.

Подумайте над последствиями применения этого к string_view. У вас есть этот тип представления, и вы хотите изменить его с помощью многоэтапного процесса. Но вы хотите сохранить исходный вид без изменений. Естественно, вы копируете ... о, подождите, string_view не является регулярным, поэтому вы сделали его незаконным только для предотвращения string_view sv = prvalue;. К сожалению. Так что теперь я должен вернуться к своему первоисточнику string_view, чтобы получить еще один. Предполагая, что у меня есть доступ к источнику, я не просто передал его как параметр const&.

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

Итак, гарантированно ли elision "концептуально правильна"? Что ж, одно из главных оправданий гарантированного исключения состояло в том, чтобы позволить функциям, которые возвращают prvalues, работать даже для типов, которые являются неподвижными. Это на самом деле очень важно. До C ++ 17, если вы хотели предоставить функции фабрики классов, но сам класс должен был быть логически неподвижным, вам не повезло. Вы можете выбрать один или другой, но не оба.

Давайте рассмотрим ваш пример subrange. В C ++ 14, если бы я хотел написать функцию, которая просто обеспечивала значения по умолчанию для некоторых параметров конструктора, или использовал конкретный контейнер, или что-то еще, это было невозможно. Я даже не мог написать контейнер, в котором была функция, которая вернула a subrange, так как мне пришлось бы создать ее, которая (если я не использовал необработанный список фигурных скобок, который не являетсявсегда возможно) провоцирует копирование / перемещение.

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

Поведение C ++ 17 делает ваши неподвижные нерегулярные типы более полезными и способными. Разве это не «концептуально правильно»?

...