Что такое value в c
Перейти к содержимому

Что такое value в c

  • автор:

тег документации

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

Синтаксис

/// property-description 

Параметры

property-description
Описание свойства.

Замечания

Скомпилируйте их для /doc обработки примечаний документации к файлу.

Пример

// xml_value_tag.cpp // compile with: /LD /clr /doc // post-build command: xdcmake xml_value_tag.dll using namespace System; /// Text for class Employee. public ref class Employee < private: String ^ name; /// Name accesses the value of the name data member public: property String ^ Name < String ^ get() < return name; >void set(String ^ i) < name = i; >> >; 

См. также

Обратная связь

Были ли сведения на этой странице полезными?

value (Справочник по C#)

Контекстное ключевое слово value используется в методе доступа set в объявлениях свойства и индексатора. Оно аналогично входному параметру метода. Ключевое слово value ссылается на значение, которое клиентский код пытается присвоить свойству или индексатору. В приведенном ниже примере класс MyDerivedClass имеет свойство с именем Name , в котором используется параметр value для присвоения новой строки резервному полю name . С точки зрения клиентского кода эта операция выглядит как простое присвоение.

class MyBaseClass < // virtual auto-implemented property. Overrides can only // provide specialized behavior if they implement get and set accessors. public virtual string Name < get; set; >// ordinary virtual property with backing field private int _num; public virtual int Number < get < return _num; >set < _num = value; >> > class MyDerivedClass : MyBaseClass < private string _name; // Override auto-implemented property with ordinary property // to provide specialized accessor behavior. public override string Name < get < return _name; >set < if (!string.IsNullOrEmpty(value)) < _name = value; >else < _name = "Unknown"; >> > > 

Дополнительные сведения см. в статьях Свойства и Индексаторы.

Спецификация языка C#

Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.

См. также

  • Справочник по C#
  • Руководство по программированию на C#
  • Ключевые слова в C#

Совместная работа с нами на GitHub

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

Что такое value в c

Кроме обычных методов в языке C# предусмотрены специальные методы доступа, которые называют свойства . Они обеспечивают простой доступ к полям классов и структур, узнать их значение или выполнить их установку.

Определение свойств

Стандартное описание свойства имеет следующий синтаксис:

[модификаторы] тип_свойства название_свойства < get < действия, выполняемые при получении значения свойства>set < действия, выполняемые при установке значения свойства>>

Вначале определения свойства могут идти различные модификаторы, в частности, модификаторы доступа. Затем указывается тип свойства, после которого идет название свойства. Полное определение свойства содержит два блока: get и set .

В блоке get выполняются действия по получению значения свойства. В этом блоке с помощью оператора return возвращаем некоторое значение.

В блоке set устанавливается значение свойства. В этом блоке с помощью параметра value мы можем получить значение, которое передано свойству.

Блоки get и set еще называются акссесорами или методами доступа (к значению свойства), а также геттером и сеттером.

Person person = new Person(); // Устанавливаем свойство - срабатывает блок Set // значение "Tom" и есть передаваемое в свойство value person.Name = "Tom"; // Получаем значение свойства и присваиваем его переменной - срабатывает блок Get string personName = person.Name; Console.WriteLine(personName); // Tom class Person < private string name = "Undefined"; public string Name < get < return name; // возвращаем значение свойства >set < name = value; // устанавливаем новое значение свойства >> >

Здесь в классе Person определено приватное поле name , которая хранит имя пользователя, и есть общедоступное свойство Name . Хотя они имеют практически одинаковое название за исключением регистра, но это не более чем стиль, названия у них могут быть произвольные и не обязательно должны совпадать.

Через это свойство мы можем управлять доступом к переменной name . В свойстве в блоке get возвращаем значение поля:

А в блоке set устанавливаем значение переменной name. Параметр value представляет передаваемое значение, которое передается переменной name.

В программе мы можем обращаться к этому свойству, как к обычному полю. Если мы ему присваиваем какое-нибудь значение, то срабатывает блок set , а передаваемое значение передается в параметр value :

person.Name = "Tom";

Если мы получаем значение свойства, то срабатывает блок get , который по сути возвращает значение переменной name:

string personName = person.Name;

То есть по сути свойство Name ничего не хранит, оно выступает в роли посредника между внешним кодом и переменной name.

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

Person person = new Person(); Console.WriteLine(person.Age); // 1 // изменяем значение свойства person.Age = 37; Console.WriteLine(person.Age); // 37 // пробуем передать недопустимое значение person.Age = -23; // Возраст должен быть в диапазоне от 1 до 120 Console.WriteLine(person.Age); // 37 - возраст не изменился class Person < int age = 1; public int Age < set < if (value < 1 || value >120) Console.WriteLine("Возраст должен быть в диапазоне от 1 до 120"); else age = value; > get < return age; >> >

В данном случае переменная age хранит возраст пользователя. Напрямую мы не можем обратиться к этой переменной — только через свойство Age. Причем в блоке set мы устанавливаем значение, если оно соответствует некоторому разумному диапазону. Поэтому при передаче свойству Age значения, которое не входит в этот диапазон, значение переменной не будет изменяться:

person.Age = -23;
Консольный вывод программы:

1 37 Возраст должен быть в диапазоне от 1 до 120 37

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

Свойства только для чтения и записи

Блоки set и get не обязательно одновременно должны присутствовать в свойстве. Если свойство определяет только блок get , то такое свойство доступно только для чтения — мы можем получить его значение, но не установить.

И, наоборот, если свойство имеет только блок set , тогда это свойство доступно только для записи — можно только установить значение, но нельзя получить:

Person person = new Person(); // свойство для чтения - можно получить значение Console.WriteLine(person.Name); // Tom // но нельзя установить // person.Name = "Bob"; // ! Ошибка // свойство для записи - можно устновить значение person.Age = 37; // но нелзя получить // Console.WriteLine(person.Age); // ! Ошибка person.Print(); class Person < string name = "Tom"; int age = 1; // свойство только для записи public int Age < set < age = value; >> // свойство только для чтения public string Name < get < return name; >> public void Print()=> Console.WriteLine($"Name: Age: "); >

Здесь свойство Name доступно только для чтения, поскольку оно имеет только блок get :

public string Name < get < return name; >>

Мы можем получить его значение, но НЕ можем установить:

Console.WriteLine(person.Name); // получить можно person.Name = "Bob"; // ! Ошибка - установить нельзя

А свойство Age, наоборот, доступно только для записи, поскольку оно имеет только блок set :

public int Age < set < age = value; >>

Можно установить его значение, но нельзя получить:

person.Age = 37; // установить можно Console.WriteLine(person.Age); // ! Ошибка - получить значение нельзя

Вычисляемые свойства

Свойства необязательно связаны с определенной переменной. Они могут вычисляться на основе различных выражений

Person tom = new("Tom", "Smith"); Console.WriteLine(tom.Name); // Tom Smith class Person < string firstName; string lastName; public string Name < get < return $""; > > public Person(string firstName, string lastName) < this.firstName = firstName; this.lastName = lastName; >>

В данном случае класс Person имеет свойство Name, которое доступно только для чтения и которое возвращает общее значение на основе значений переменных firstName и lastName.

Модификаторы доступа

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

Person tom = new("Tom"); // Ошибка - set объявлен с модификатором private //tom.Name = "Bob"; Console.WriteLine(tom.Name); // Tom class Person < string name = ""; public string Name < get < return name; >private set < name = value; >> public Person(string name) => Name = name; >

Теперь закрытый блок set мы сможем использовать только в данном классе — в его методах, свойствах, конструкторе, но никак не в другом классе:

При использовании модификаторов в свойствах следует учитывать ряд ограничений:

  • Модификатор для блока set или get можно установить, если свойство имеет оба блока (и set, и get)
  • Только один блок set или get может иметь модификатор доступа, но не оба сразу
  • Модификатор доступа блока set или get должен быть более ограничивающим, чем модификатор доступа свойства. Например, если свойство имеет модификатор public, то блок set/get может иметь только модификаторы protected internal, internal, protected, private protected и private

Автоматические свойства

Свойства управляют доступом к полям класса. Однако что, если у нас с десяток и более полей, то определять каждое поле и писать для него однотипное свойство было бы утомительно. Поэтому в .NET были добавлены автоматические свойства. Они имеют сокращенное объявление:

class Person < public string Name < get; set; >public int Age < get; set; >public Person(string name, int age) < Name = name; Age = age; >>

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

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

Стоит учитывать, что нельзя создать автоматическое свойство только для записи, как в случае со стандартными свойствами.

Автосвойствам можно присвоить значения по умолчанию (инициализация автосвойств):

Person tom = new(); Console.WriteLine(tom.Name); // Tom Console.WriteLine(tom.Age); // 37 class Person < public string Name < get; set; >= "Tom"; public int Age < get; set; >= 37; >

И если мы не укажем для объекта Person значения свойств Name и Age, то будут действовать значения по умолчанию.

Автосвойства также могут иметь модификаторы доступа:

class Person < public string Name < private set; get;>public Person(string name) => Name = name; >

Мы можем убрать блок set и сделать автосвойство доступным только для чтения. В этом случае для хранения значения этого свойства для него неявно будет создаваться поле с модификатором readonly, поэтому следует учитывать, что подобные get-свойства можно установить либо из конструктора класса, как в примере выше, либо при инициализации свойства:

class Person < // через инициализацию свойства public string Name < get; >= "Tom"; // через конструктор public Person(string name) => Name = name; >

Блок init

Начиная с версии C# 9.0 сеттеры в свойствах могут определяться с помощью оператора init (от слова «инициализация» — это есть блок init призван инициализировать свойство). Для установки значений свойств с init можно использовать только инициализатор, либо конструктор, либо при объявлении указать для него значение. После инициализации значений подобных свойств их значения изменить нельзя — они доступны только для чтения. В этом плане init-свойства сближаются со свойствами для чтения. Разница состоит в том, что init-свойства мы также можем установить в инициализаторе (свойства для чтения установить в инициализаторе нельзя). Например:

Person person = new(); //person.Name = "Bob"; //! Ошибка - после инициализации изменить значение нельзя Console.WriteLine(person.Name); // Undefined public class Person < public string Name < get; init; >= "Undefined"; >

В данном случае класс Person для свойства Name вместо сеттера использует оператор init . В итоге на строке

Person person = new();

предполагается создание объекта с инициализацией всех его свойств. В данном случае свойство Name получит в качестве значения строку «Undefined». Однако поскольку инициализация свойства уже произошла, то на строке

person.Name = "Bob"; // Ошибка

мы получим ошибку.

Как можно установить подобное свойство? Выше продемонстрирован один из способов — установка значения при определении свойства. Второй способ — через конструктор:

Person person = new("Tom"); Console.WriteLine(person.Name); // Tom public class Person < public Person(string name) =>Name = name; public string Name < get; init; >>

Третий способ — через инициализатор:

Person person = new() < Name = "Bob">; Console.WriteLine(person.Name); // Bob public class Person < public string Name < get; init; >= ""; >

В принцпе есть еще четвертый способ — установка через другое свойство с модификатором init :

var person = new Person() < Name = "Sam" >; Console.WriteLine(person.Name); // Sam Console.WriteLine(person.Email); // Sam@gmail.com public class Person < string name = ""; public string Name < get < return name; >init < name = value; Email = $"@gmail.com"; > > public string Email < get; init; >= ""; >

В данном случае свойство Name управляет полем для чтения name . Благодаря этому перед установкой значения свойства мы можем произвести некоторую предобработку. Кроме того, в выражении init устанавливается другое init-свойство — Email, которое для установки значения использует значение свойства Name — из имени получаем значение для электронного адреса.

Причем если при объявлении свойства указано значение, то в конструкторе мы можем его изменить. Значение, установленное в конструкторе, можно изменить в инициализаторе. Однако дальше процесс инициализации заканчивается. И значение не может быть изменено.

Сокращенная запись свойств

Как и методы, мы можем сокращать определения свойств. Поскольку блоки get и set представляют специальные методы, то как и обычные методы, если они содержат одну инструкцию, то мы их можем сократить с помощью оператора => :

class Person < string name; public string Name < get =>name; set => name = value; > >

Также можно сокращать все свойство в целом:

class Person < string name; // эквивалентно public string Name < get < return name; >> public string Name => name; >

модификатор required

Модификатор required (добавлен в C# 11) указывает, что поле или свойства с этим модификатором обязательно должны быть инициализированы. Например, в следующем примере мы получим ошибку:

Person tom = new Person(); // ошибка - свойства Name и Age не инициализированы public class Person < public required string Name < get; set; >public required int Age < get; set; >>

Здесь свойства Name и Age отмечены как обязательные для инициализации с помощью модификатора required , поэтому необходимо использовать инициализатор для их инициализации:

Person tom = new Person < Name = "Tom", Age = 38 >; // ошибки нет

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

Person bob = new Person("Bob"); // ошибка - свойства Name и Age все равно надо установить в инициализаторе public class Person < public Person(string name) < Name = name; >public required string Name < get; set; >public required int Age < get; set; >= 22; >

Категории выражений в C++

Категории выражений, такие как lvalue и rvalue, относятся, скорее, к фундаментальным теоретическим понятиям языка C++, чем к практическим аспектам его использования. По этой причине многие даже опытные программисты достаточно смутно представляют себе, что они означают. В этой статье я постараюсь максимально просто объяснить значение этих терминов, разбавляя теорию практическими примерами. Сразу оговорюсь: статья не претендует на максимально полное и строгое описание категорий выражений, за подробностями я рекомендую обращаться непосредственно в первоисточник: Стандарт языка C++.

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

Немного истории

Термины lvalue и rvalue появились ещё в языке C. Стоит отметить, что путаница была заложена в терминологию изначально, потому как относятся они к выражениям (expressions), а не к значениям (values). Исторически lvalue – это то, что может быть слева (left) от оператора присваивания, а rvalue – то, что может быть только справа (right).

lvalue = rvalue;

Однако, такое определение несколько упрощает и искажает суть. Стандарт C89 определял lvalue как object locator, т.е. объект с идентифицируемым местом в памяти. Соответственно, всё, что не подходило под это определение, входило в категорию rvalue.

Бьярн спешит на помощь

В языке C++ терминология категорий выражений достаточно сильно эволюционировала, в особенности после принятия Стандарта C++11, где вводились понятия rvalue-ссылок и семантики перемещения (move semantics). История появления новой терминологии интересно описана в статье Страуструпа “New” Value Terminology.

В основу новой более строгой терминологии легли 2 свойства:

  • наличие идентичности (identity) – т. е. какого-то параметра, по которому можно понять, ссылаются ли два выражения на одну и ту же сущность или нет (например, адрес в памяти);
  • возможность перемещения (can be moved from) – поддерживает семантику перемещения.

Обладающие идентичностью выражения обобщены под термином glvalue (generalized values), перемещаемые выражения называются rvalue. Комбинации двух этих свойств определили 3 основные категории выражений:

Обладают идентичностью Лишены идентичности
Не могут быть перемещены lvalue
Могут быть перемещены xvalue prvalue

На самом деле, в Стандарте C++17 появилось понятие избегание копирования (copy elision) – формализация ситуаций, когда компилятор может и должен избегать копирования и перемещения объектов. В связи с этим, prvalue не обязательно могут быть перемещены. Подробно и с примерами об этом можно почитать вот тут. Впрочем, это не влияет на понимание общей схемы категорий выражений.

В современном Стандарте C++ структура категорий приводится в виде вот такой схемы:

image

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

glvalue

Выражения категории glvalue обладают следующими свойствами:

  • могут быть неявно преобразованы в prvalue;
  • могут быть полиморфными, т. е. для них имеют смысл понятия статического и динамического типа;
  • не могут иметь тип void – это напрямую следует из свойства наличия идентичности, ведь для выражений типа void нет такого параметра, который позволил бы отличать их одно от другого;
  • могут иметь неполный тип (incomplete type), например, в виде forward declaration (если это разрешено для конкретного выражения).

rvalue

Выражения категории rvalue обладают следующими свойствами:

  • нельзя получить адрес rvalue в памяти – это напрямую следует из свойства отсутствия идентичности;
  • не могут находиться в левой части оператора присваивания или составного присваивания;
  • могут использоваться для инициализации константной lvalue-ссылки или rvalue-ссылки, при этом время жизни объекта расширяется до времени жизни ссылки;
  • если используются как аргумент при вызове функции, у которой есть 2 перегруженные версии: одна принимает константную lvalue-ссылку, а другая – rvalue-ссылку, то выбирается версия, принимающая rvalue-ссылку. Именно это свойство используется при реализации семантики перемещения (move semantics):
class A < public: A() = default; A(const A&) < std::cout A(A&&) < std::cout >; . A a; A b(a); // Вызывается A(const A&) A c(std::move(a)); // Вызывается A(A&&)

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

lvalue

  • все свойства glvalue (см. выше);
  • можно взять адрес (используя встроенный унарный оператор & );
  • модифицируемые lvalue могут находиться в левой части оператора присваивания или составных операторов присваивания;
  • могут использоваться для инициализации ссылки на lvalue (как константной, так и неконстантной).

К категории lvalue относятся следующие выражения:

  • имя переменной, функции или поле класса любого типа. Даже если переменная является rvalue-ссылкой, имя этой переменной в выражении является lvalue;
void func() <> . auto* func_ptr = &func; // порядок: получаем указатель на функцию auto& func_ref = func; // порядок: получаем ссылку на функцию int&& rrn = int(123); auto* pn = &rrn; // порядок: получаем адрес объекта auto& rn = rrn; // порядок: инициализируем lvalue-ссылку
  • вызов функции или перегруженного оператора, возвращающего lvalue-ссылку, либо выражение преобразования к типу lvalue-ссылки;
  • встроенные операторы присваивания, составные операторы присваивания ( = , += , /= и т. д.), встроенные преинкремент и предекремент ( ++a , —b ), встроенный оператор разыменования указателя ( *p );
  • встроенный оператор обращения по индексу ( a[n] или n[a] ), когда один из операндов – lvalue массив;
  • вызов функции или перегруженного оператора, возвращающего rvalue-ссылку на функцию;
  • строковый литерал, например «Hello, world!» .

Строковый литерал отличается от всех остальных литералов в языке C++ именно тем, что является lvalue (хотя и неизменяемым). Например, можно получить его адрес:

auto* p = &”Hello, world!”; // тут константный указатель, на самом деле

prvalue

  • все свойства rvalue (см. выше);
  • не могут быть полиморфными: статический и динамический типы выражения всегда совпадают;
  • не могут быть неполного типа (кроме типа void, об этом будет сказано ниже);
  • не могут иметь абстрактный тип или быть массивом элементов абстрактного типа.

К категории prvalue относятся следующие выражения:

  • литерал (кроме строкового), например 42 , true или nullptr ;
  • вызов функции или перегруженного оператора, который возвращает не ссылку ( str.substr(1, 2) , str1 + str2 , it++ ) или выражение преобразования к нессылочному типу (например static_cast(x) , std::string<> , (int)42 );
  • встроенные постинкремент и постдекремент ( a++ , b— ), встроенные математические операции ( a + b , a % b , a & b , a = b , и т.д.), встроенная операция взятия адреса ( &a );
  • указатель this;
  • элемент перечисления;
  • нетиповой параметр шаблона, если он – не класс;
  • лямбда-выражение, например [](int x) < return x * x; >.

xvalue

Примеры выражений категории xvalue:

  • вызов функции или встроенного оператора, возвращающего rvalue-ссылку, например std::move(x);

и в самом деле, для результата вызова std::move() нельзя получить адрес в памяти или инициализировать им ссылку, но в то же время, это выражение может быть полиморфным:

struct XA < virtual void f() < std::cout >; struct XB : public XA < virtual void f() < std::cout >; XA&& xa = XB(); auto* p = &std::move(xa); // ошибка auto& r = std::move(xa); // ошибка std::move(xa).f(); // выведет “XB::f()”
  • встроенный оператор обращения по индексу ( a[n] или n[a] ), когда один из операндов – rvalue-массив.

Некоторые особые случаи

Оператор запятая

Для встроенного оператора запятая (comma operator) категория выражения всегда соответствует категории выражения второго операнда.

int n = 0; auto* pn = &(1, n); // lvalue auto& rn = (1, n); // lvalue 1, n = 2; // lvalue auto* pt = &(1, int(123)); // ошибка, rvalue auto& rt = (1, int(123)); // ошибка, rvalue

Выражения типа void

Вызовы функций, возвращающих void, выражения преобразования типов к void, а также выбрасывания исключений (throw) считаются выражениями категории prvalue, но их нельзя использовать для инициализации ссылок или в качестве аргументов функций.

Тернарный оператор сравнения

Определение категории выражения a ? b : c – случай нетривиальный, всё зависит от категорий второго и третьего аргументов ( b и c ):

  • если b или c имеют тип void, то категория и тип всего выражения соответствуют категории и типу другого аргумента. Если оба аргумента имеют тип void, то результат – prvalue типа void;
  • если b и c являются glvalue одного типа, то и результат является glvalue этого же типа;
  • в остальных случаях результат prvalue.

Для тернарного оператора определён целый ряд правил, по которым к аргументам b и c могут применяться неявные преобразования, но это несколько выходит за темы статьи, интересующимся рекомендую обратиться к разделу Стандарта Conditional operator [expr.cond].

int n = 1; int v = (1 > 2) ? throw 1 : n; // lvalue, т.к. throw имеет тип void, соответственно берём категорию n ((1 < 2) ? n : v) = 2; // тоже lvalue, выглядит странно, но работает ((1 < 2) ? n : int(123)) = 2; // так не получится, т.к. теперь всё выражение prvalue

Обращения к полям и методам классов и структур

Для выражений вида a.m и p->m (тут речь о встроенном операторе -> ) действуют следующие правила:

  • если m – элемент перечисления или нестатический метод класса, то всё выражение считается prvalue (хотя ссылку таким выражением инициализировать не получится);
  • если a – это rvalue, а m – нестатическое поле нессылочного типа, то всё выражение относится к категории xvalue;
  • в остальных случаях это lvalue.

Для указателей на члены класса ( a.*mp и p->*mp ) правила похожие:

  • если mp – это указатель на метод класса, то всё выражение считается prvalue;
  • если a – это rvalue, а mp – указатель на поле данных, то всё выражение относится к xvalue;
  • в остальных случаях это lvalue.

Битовые поля

Битовые поля – удобный инструмент для низкоуровнего программирования, однако, их реализация несколько выпадает из общей структуры категорий выражений. Например, обращение к битовому полю вроде бы является lvalue, т. к. может присутствовать в левой части оператора присваивания. В то же время, взять адрес битового поля или инициализировать им неконстантную ссылку не получится. Константную ссылку на битовое поле инициализировать можно, но при этом будет создана временная копия объекта:

struct BF < int f:3; >; BF b; b.f = 1; // OK auto* pb = &b.f; // ошибка auto& rb = b.f; // ошибка

Вместо заключения

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *