You are on page 1of 8

18.

რეფერენსები
განხილული საკითხები:
 მარცხენა და მარჯვენა სიდიდეები (lvalue, rvalue)
 lvalue -ზე რეფერენსი >>>
 rvalue-ზე რეფერენსი >>>
 რეფერენსების ჩამოყრა >>>
 უნივერსალური რეფერენსი (გადამწოდებელი რეფერენსი) >>>
 წყაროები >>>

მარცხენა და მარჯვენა სიდიდეები (lvalue, rvalue)


C++ ენაში, ყოველი გამოსახულება არის ან მარცხენა სიდიდეა (lvalue) ან მარჯვენა (rvalue).
მარცხენა სიდიდედ იწოდება გამოსახულება, რომელიც არსებობას აგრძელებს ცალკეული
გამოსახულების მიღმა. ზუსტი განმარტება რთულია. ზოგადად, შეგიძლიათ იფიქროთ
მარცხენა სიდიდეზე როგორც სახელის მქონე გამოსახულებაზე. ამბობენ ასევე, რომ მარცხენა
სიდიდე წარმოადგენს გამოსახულებას, რომელსაც უკავია განსაზღვრული ადგილი
მეხსიერებაში, თუმცა ეს საკამათოა, - დასაბრუნებელი მნიშვნელობის მქონე ფუნქციის
გამოძახების შედეგი ითვლება მარჯვენა გამოსახულებად, ხოლო თუ ის რეფერენსით აბრუნებს
მნიშვნელობას, მაშინ აქვს ადგილი მეხსიერებაში და მისთვის შეიძლება რაღაცეების მინიჭება.
მარჯვება სიდიდეები განისაზღვრება გამორიცხვით, რადგან გამოსახულება არის ან მარჯვენა,
ან მარცხენა სიდიდე.
განვიხილოთ მაგალითი:
int main()
{
int x = 3 + 4;
cout << x << endl;
}
აქ, x არის lvalue, რადგან ის შენარჩუნდება მისი განმსაზღვრელი გამოსახულების შემდეგ. 3+4
არის rvalue.
შემდეგი მაგალითი გვიჩვენებს lvalue და rvalue-ების რამდენიმე სწორ და არასწორ გამოყენებას:
int main()
{
int i, j, *p;
// სწორია: i ცვლადი არის lvalue.
i = 7;
// არასწორია: მარცხენა ოპერანდი უნდა იყოს lvalue (C2106).
7 = i; // C2106
j * 4 = 7; // C2106
// სწორია: განმისამართებული პოინტერი არის lvalue.
*p = i;
const int ci = 7;
// არასწორია: ცვლადი არის არა-მოდიფიცირებადი lvalue (C3892).
ci = 9; // C3892
// სწორია: the პირობითი ოპერატორი აბრუნებს lvalue-ს.
((i < 3) ? i : j) = 7;
}
ამ მაგალითებში ვგულისხმობთ რომ ოპერაციები არ არის გადატვირთული. თუ
გადავტვირთავთ, შეგვიძლია j * 4 გამოსახულება ვაქციოთ lvalue-დ..

Page 1 of 8
უფრო საინტერესო მაგალითები უკავშირდება ფუნქციებს. ფუნქციის გამოძახება წარმოადგენს
rvalue-ს, თუ ფუნქცია აბრუნებს მნიშვნელობას. თუ რეფერენსით აბრუნებს- მაშინ lvalue-საც.
მაგალითად, back() მეთოდი ვექტორში.
თავიდან C ენაში განსაზღვრეს lvalue, როგორც „სიდიდე რომელიც შესაფერისია მინიჭების
მარცხენა მხარისთვის“. მოგვიანებით ISO C-ში დაამატეს const სიტყვა. თუ ობიექტის
განაცხადში მონაწილეობს const, ეს ობიექტი არ არის განახლებადი (modifiable).
შესაძლებელია lvalue-ების გარდაქმნა rvalue-ებად, რასაც ხშირად შევხვდებით შემდეგში.
პირიქით შეუძლებელია გარდაქმნა.

<<< lvalue-ზე რეფერენსი


ყველაზე კარგად ნაცნობი არის lvalue-ზე რეფერენსი. თუ T ტიპის lvalue ობიექტი სახელით x
უკვე არის განსაზღვრული, მაშინ ჩანაწერი
T& y = x; //ან ინიციალების სხვა რაიმე ფორმა, მაგ. () ან {}

ნიშნავს, რომ x ცვლადს დაერქვა მეორე სახელი y, და ეს ობიექტები მეხსიერების ერთი და იმავე
ნაკვეთს აღნიშნავენ. შესაძლებელია რომ უკვე არსებულ lvalue ობიექტს დავარქვათ მეორე
სახელი, რომელსაც შეზღუდული ექნება ობიექტის განახლების შესაძლებლობა:
const T& y = x; // (1)

ამ განაცხადის შემდეგ, y არის მუდმივი რეფერენსი ჩვეულებრივ lvalue ობიექტზე. y სახელით


მხოლოდ ამოკითხვა შეგვიძლია და არა ჩაწერა.
თუ x იქნებოდა მუდმივი, მაშინ მისი რეფერენსი მხოლოდ მუდმივი შეიძლება იყოს, ანუ
მხოლოდ (1) განაცხადი არის მისაღები.
lvalue-ზე მუდმივი რეფერენსით შესაძლებელია მითითება მუდმივ ან დროებით ობიექტებზე.
მაგალითად:
int intF()
{
int i(55);
return i;
}
int main()
{
const int& cr = intF();
const int& cr1 = 747;
}

შევნიშნოთ, რომ ინციალიზაციის შემდეგ lvalue-ზე რეფერენსი არის ჩვეულებრივი lvalue


ობიექტი.

<<< rvalue-ზე რეფერენსი


rvalue-ზე რეფერენსი ქმნის ახალ lvalue ობიექტს, რომელიც აუცილებლად განაცხადის
გაკეთების მომენტში ინიციალდება და აუცილებლად rvalue სიდიდით.
T&& რეფერენსის სახელი = rvalue სიდიდე //ან ინიციალების სხვა ფორმა, მაგ. () ან {}

ინიციალების შემდეგ, ეს არის ჩვეულებრივი lvalue ობიექტი, რომელსაც შეუძლია თავის


მნიშვნელობის განახლება T ტიპის სხვა ობიექტების მნიშვნელობებით (მინიჭებით, თუ ეს T
ტიპისთვის დაშვებულია, ან გადაადგილებით - თუ დაშვებულია, ან ორივეთი). მაგრამ იმის
გამო რომ იგი არის rvalue რეფერენსი, მისი გამოყენება შესაძლებელია სხვა მიზნებითაც, რაზეც
ვისაუბრებთ გადაადგილებასთან და უნაკლო გადაწოდებასთან დაკავშირებით.
მაგალითად, სწორია
Page 2 of 8
vector<int>&& b{ 1,2,3,2,1 };
და ამის შემდეგ შესაძლოა მოდიოდეს:
vector<int> a{ 8,7,5,3,2,9 };
b = a;
მაგრამ არასწორია
vector<int> a{ 8,7,5,3,2,9 };
vector<int>&& b{a};
რადგან rvalue-ზე რეფერენსი არ შეიძლება მიებას lvalue-ს.
ასეთი რეფერენსების ინიციალება შესაძლებელია ისეთი ფუნქციის გამოძახებით.

<<< რეფერენსების ჩამოყრა


C++11-ში ძალიან მნიშვნელოვან საქმეს აკეთებს ე.წ. რეფერენსების ჩამოყრის წესი (Reference
collapsing). თუმცა, ჯერ უნდა გავარკვიოთ თუ რას ნიშნავს და როგორ ხდება თვითონ
რეფერენსების დაგროვება.
C++11-მდე, C++ ენაში რეფერენსების რეფერირების საკითხი რთულად იყო გადაწყვეტილი.
C++11-ში, რეფერენსების დაგროვება (და ამავდროულად ჩამოყრაც - collapsing) ხდება როდესაც
რეფერესების რეფერენსები გამოყენებულია ერთ-ერთ შემდეგ ვითარებაში:
 განმსაზღვრელი decltype,
 typedef სახელი;
 თარგის ტიპის პარამეტრი.
საქმე იმაშია, რომ შეგვიძლია განვსაზღვროთ ცვლადი, რომლის ტიპი RT არის რეფერენსი T
ტიპზე, სადაც T აგრეთვე წარმოადგენს რეფერენს-ტიპს. მაგალითად:
// T აღნიშნავს int& ტიპს
typedef int& T;
// იგივეა რაც using T = add_lvalue_reference_t<int>;
// TR არის lvalue რეფერენსი T-ზე
typedef T& TR;
// var-ის ტიპად გამოცხადებლია TR
TR var;
როგორც ვხედავთ, შექმნილ ტიპებში ხდება მათ მიერ შეფუთული რეფერენსების დაგროვება.
ახალ-ახალი ტპების შექმნა და რეფერენსების შეფუთვა შესაძლებელია გავაგრძელოთ, მაგრამ
შექმნილი ცვლადების რეალური ტიპი იქნება ან მარცხენა, ან მარჯვენა რეფერენსი, მხოლოდ.
პროგრამებისთვის (ძირითადად ფუნქციის რეფერენსი პარამეტრისთვის) არსებითია თუ
როგორ ხდება პარამეტრის ინიციალება - rvalue-თი თუ lvalue-თი. დანარჩენი, ისტორიასთან
დაკავშირებული დეტალები - მაგალითად, ასეთი lvalue თავის მხრივ როგორ იყო
ინიციალებული, rvalue-თი თუ lvalue-თი, - არაფერს არ წყვეტს.
შემდეგი ცხრილი გვიჩვენებს თუ სინამდვილეში რა არის var-ის ტიპი სხვადასხვა შემთხვევაში,
როდესაც არც TR და არც T არაა cv-დახასიათებული (const ან volatile). განმარტების მიხედვით,
გასაღები სიტყვა volatile ახასიათებს ტიპს, როგორც ობიექტს, რომელიც გადაკეთებადია
პროგრამაში ოპერაციული სისტემის, აპარატურის, ან ერთდროულად შესრულებადი დინებების
მიერ.
T TR var-ის ტიპი
1 A T A1

Page 3 of 8
2 A T& A&1
3 A T&& A&&1
4 A& T A&1
5 A& T& A&
6 A& T&& A&
7 A&& T A&&1
8 A&& T& A&
9 A&& T&& A&&
შენიშვნა
1
. რეფერენსების ჩამოყრის წესი არ მუშაობს როდესაც T და TR, არცერთი არაა რეფერენსის ტიპი

შევეცადოთ შინაარსობრივად ავხსნათ, თუ რატომ ხდება ასე. განვიხილოთ მხოლოდ ის


შემთხვევები, როდესაც T და RT რეფერენსის ტიპებია.
შემთხვევა 5. როდესაც T არის A&, ხოლო RT არის T&. გავიხილოთ ორი განაცხადი:
A a;
T x{ a }; // (2)
RT y{ x }; // (3)
(2) ნიშნავს, რომ a ცვლადს (ანუ მონაკვეთს მეხსიერებაში) დაექვა მეორე სახელი x. (3) ნიშნავს,
რომ იგივე a ცვლადს დაექვა y. ახლა გასაგებია, რომ როგორიც არის x, ისეთივეა y.
შემთხვევა 6. როდესაც T არის A&, ხოლო RT არის T&&. სიმარტივისთვის, განვიხილოთ მარტივი
შემთხვევა, როდესაც A არის მთელი რიცხვების ტიპი. RT ტიპის ცვლადის შექმნისა და
ინიციალებისთვის ჯერ მისთვის უნდა გავამზადოთ T ტიპის მარჯვენა მნიშვნელობა. ეს
ყველაზე ადვილად გაკეთდება თუ ინიციალებისთვის გამოვიყენებთ ფუნქციის დასაბრუნებელ
მნიშვნელობას. მაგალითად:
int& rFoo()
{
int* p = new int { 777 };
cout << "p=" << p << endl;
return *p;
}
ახლა განვიხილოთ ასეთი მთავარი პროგრამა:
int main()
{
using T = add_lvalue_reference_t<int>; // T = int&
T&& y{ rFoo() }; // y-ის ტიპი არის T&& არის int & &&
cout << "&y=" << &y << endl;
}

ამავე დროს, შედეგი

p=004FF9F0
&y=004FF9F0
Press any key to continue . . .
გვიჩვენებს რომ y არის რეფერენსი მეხსიერების იმ მონაკვეთზე, რომელიც შეიქმნა ფუნქციაში
და რომელიც დაბრუნდა რეფერენსით. ფუნქცია ძალიან ხელოვნურია, თუმცა
თვალსაჩინოებისთვის გამოსადეგი.

Page 4 of 8
შემთხვევა 8. როდესაც T არის A&&, ხოლო RT არის T&. განვიხილოთ მარტივი შემთხვევა,
როდესაც A არის მთელი რიცხვების ტიპი. მარჯვენა მნიშვნელობა გავამზადოთ შემდეგი
ფუნქციის გამოძახებით:
int foo()
{
return 5;
}
განვიხილოთ:
int main()
{
using T = add_rvalue_reference_t<int>;
T x{ foo() }; // (4)
cout << "&x=" << &x << endl;
T& y{ x };
cout << "&y=" << &y << endl;
}
(4) განაცხადის შემდეგ, x არის კონვერტირებადი ჩვეუელებრივ მთელზე, ხოლო შედეგი
&x=00B6F808
&y=00B6F808
Press any key to continue . . .
გვიჩვენებს, რომ y ებმება მთელი ტიპის ცვლადის მისამართს. შინაარსისგან განყენებულად,
საკმარისია კურსორი გადავატაროთ y ცვლადს, რომ გამოჩნდება კარნახი:

რომელიც გვიჩვენებს რეფერენსების ჩამოყრის შედეგს.


შემდეგი ცხრილი გვიჩვენებს cv-ად დახასიათებული რეფერენსების ჩამოყრის წესს:

T TR var-ის ტიპი

1 A const T const A1
2 const A volatile T& const volatile A&1
3 A const T&& const A&&1
4 A& const T A&1
5 const A& volatile T& const A&
6 const A& T&& const A&
7 A&& const T A&&1
8 const A&& volatile T& const A&
9 const A&& T&& const A&&
შენიშვნა
1
. რეფერენსების ჩამოყრის წესი არ მუშაობს როდესაც T და TR არცერთი არაა რეფერენსის ტიპი

აქ კანონზომიერება ასეთია: როდესაც T არის რეფერენსის ტიპის, var ცვლადის ტიპი


მემკვიდრეობს მხოლოდ T-ს cv-დახასიათებას.

Page 5 of 8
<<< უნივერსალური რეფერენსი (გადამწოდებელი რეფერენსი)
იმისათვის რომ გავაკეთოთ განაცხადი რაიმე T ტიპის rvalue-ზე რეფერენსი, ვწერთ T&& -ს.
მაგრამ ზოგიერთ შემთხვევში შექმნილი ობიექტი აღმოჩნდება სხვა ტიპის. მაგალითად:
void f(Widget&& param); // rvalue-ზე რეფერენსი
Widget&& var1 = Widget(); // rvalue-ზე რეფერენსი
auto&& var2 = var1; // ეს არაა rvalue-ზე რეფერენსი
template<typename T>
void f(std::vector<T>&& param); // rvalue-ზე რეფერენსი
template<typename T>
void f(T&& param); // არაა rvalue-ზე რეფერენსი
„T&&“-ს აქვს ორი განსხვავებული მნიშვნელობა. ერთი არის rvalue-ზე რეფერენსი. ასეთი
რეფერენსები იქცევიან ზუსტად ისე, რასაც მისგან ელიან: ისინი ებმებიან მხოლოდ rvalue-ებს
და მათი პირველადი „არსებობის აზრი“ არის ისეთი ობიექტების ამოცნობა, რომლებიდანაც
შესაძლებელია გადაადგილება.
„T&&“-ს სხვა მნიშვნელობა არის რეფერენსი ან rvalue-ზე ან lvalue-ზე. ასეთი რეფერენსი საწყის
კოდში გამოიყურება rvalue-ზე რეფერენსივით (ე.ი.„T&&“), მაგრამ შესაძლოა მოქმედებდეს
lvalue-ზე რეფერენსის მსგავსად (ე.ი.„T&“). მათი ორმაგი ბუნება საშუალებას აძლევთ რომ მიებან
როგორც rvalue-ებს (rvalue-ზე რეფერენსების მსგავსად), ასევე lvalue-ებს (lvalue-ზე
რეფერენსების მსგავსად). შემდეგ, მათ შეუძლიათ მიებან const ან არა-const ობიექტებს, volatile ან
არა- volatile ობიექტებს, ისეთებსაც კი რომლებიც ერთდროულად ორივეა, const-იც და volatile-
იც. ასეთი უპრეცენდენტოდ მოქნილი რეფერენსები იმსახურებენ საკუთარ სახელს. სკოტ
მაიერსი მათ უწოდებს უნივერსალურ რეფერენსებს (universal references). C++ community-ის
რამდენიმე წევრი მათ მოიხსენებს სახელით: გადამწოდებელი რეფერენსი (forwarding references).
როგორც მარტივი მაგალითი, Visual Studio-ში თუ ავკრეფთ სტრიქონებს:
int&& m{ 99 };
auto&& n = m;
და კურსორს გადავატარებთ თავზე n ცვლადს, გამოჩნდება int &n გზავნილი. m ცვლადის თავზე
გადატარებისას ჩანს მის განაცხადში მითითებული ტიპი.
უნივერსალური რეფერენსი ორ ვითარებაში ჩნდება. უფრო გავრცელებული არის თარგის
პარამეტრები, როგორც ზემოთ მოყვანილ ნიმუშში იყო:
template<typename T>
void f(T&& param); // param არის უნივერსალური რეფერენსი

მეორე ვითარება არის auto განაცხადი, როგორც ზემოთ მოყვანილ მაგალითში იყო:
auto&& var2 = var1; // var2 არის უნივერსალური რეფერენსი

ამ ვითარებებს აერთიანებს ტიპის გამოცნობა. f თარგში, param-ის ტიპის გამოცნობა ხდება, და


var2 -ის განაცხადშიც ხდება ამ ცვლადის ტიპის გამოცნობა. შევადაროთ შემდეგ მაგალითებს
(ისევ ზემოთ მოყვანილი ნიმუშებიდან), სადაც არ ხდება ტიპის გამოცნობა. თუ ხედავთ „T&&“-ს
ტიპის გამოცნობის გარეშე, ე.ი. უყურებთ rvalue რეფერენსს:
void f(Widget&& param); // არაა საჭირო ტიპის გამოცნობა
// param არის rvalue რეფერენსი
Widget&& var1 = Widget(); // არაა საჭირო ტიპის გამოცნობა
// var1 არის rvalue რეფერენსი

Page 6 of 8
უნივერსალური რეფერენსი - რეფერენსია, ამიტომ ინიციალება აუცილებელია. (უნივერსალური
რეფერენსის) მაინიციალებელი განსაზღვრავს თუ ზუსტად რას წარმოადგენს უნივერსალური
რეფერენსი - rvalue-ზე რეფერენსს თუ lvalue-ზე რეფერენსს. თუ მაინიციალებელი rvalue არის,
მაშინ უნივერსალური რეფერენსი შეესაბამება rvalue-ზე რეფერენსს. თუ მაინიციალებელი lvalue
არის, მაშინ უნივერსალური რეფერენსი შეესაბამება lvalue-ზე რეფერენსს. იმ უნივერსალური
რეფერენსებისთვის, რომლებიც ფუნქციის პარამეტრებია, მაინიციალებელი მიეწოდება
გამოძახების ადგილიდან:
template<typename T>
void f(T&& param); // param არის უნივერსალური რეფერენსი

Widget w;
f(w); // lvalue მიეწოდება f-ს; param-ის ტიპი არის
// Widget& (ე.ი, lvalue რეფერენსი)

f(std::move(w)); // rvalue მიეწოდება f-ს; param-ის ტიპი არის


// Widget&& (ე.ი, rvalue რეფერენსი)
იმისათვის რომ რეფერენსი იყოს უნივერსალური, ტიპის გამოცნობა აუცილებელია, მაგრამ არა
საკმარისი. რეფერენსის განაცხადის სახე უნდა იყოს ზუსტად „T&&“. კვლავ შევხედოთ კოდს
ზემოთ მოყვანილი ნიმუშებიდან:
template<typename T>
void f(std::vector<T>&& param); // param არის rvalue-ზე რეფერენსი

როდესაც f იღვიძებს, ხდება T ტიპის გამოცნობა (გარდა იმ შემთხვევისა როდესაც


გამომძახებელი ცხადად მიუთითებს მას, ესაა უკიდურესი შემთხვევა რომელსაც არ
განვიხილავთ). მაგრამ param-ის ტიპის განაცხადის სახე არ არის „T&&“, ის არის
„std::vector<T>&&“. ეს გამორიცხავს შესაძლებლობას რომ param არის უნივერსალური
რეფერენსი. შესაბამისად, param არის rvalue-ზე რეფერენსი. ამას ხალისით დაგიდასტურებთ
თქვენი კომპილერი, თუ f- ს გადააწვდით lvalue-ს:
std::vector<int> v;
f(v); // error! can't bind lvalue to
// rvalue reference
ისიც კი საკმარისია რომ მხოლოდ const მახასიათებელი მონაწილეობდეს განაცხადში, რომ
რეფერენსი აღარ იყოს უნივერსალური:
template<typename T>
void f(const T&& param); // param არის rvalue-ზე რეფერენსი

თუ თქვენ ხართ თარგში და ხედავთ „T&&“-ს, მაინც ყოველთვის არ გაქვთ უნივერსალური


რეფერენსის შემთხვევა. ეს იმიტომ, რომ თარგში ყოფნა არ არის ტიპის გამოცნობის გარანტია.
განვიხილოთ std::vector-ის წევრი ფუნქცია push_back:
template<class T, class Allocator = allocator<T>> // C++-ის სტანდარტებიდან
class vector {
public:
void push_back(T&& x);

};
push_back-ის არგუმენტს აქვს უნივერსალური რეფერენსისთვის საჭირო სახე, მაგრამ ტიპის
გამოცნობას ადგილი არ აქვს. ეს იმიტომ რომ push_back არ არსებობს vector-ის სპეციალიზაციის
მითითების გარეშე, ხოლო სპეციალიზაციის მითითება სრულად განსაზღვრავს push_back-ს.
განაცხადი
Page 7 of 8
std::vector<Widget> v;
იწვევს std::vector თარგის შემდეგ განხორციელებას:
class vector<Widget, allocator<Widget>> {
public:
void push_back(Widget&& x); // rvalue-ზე განაცხადი

};
ახლა ცხადად ჩანს რომ push_back არ გულისხმობს ტიპის გამოცნობას. push_back (ასეთი ორი
ცალია გადატვირთული) ყოველთვის განსაზღვრავს T ტიპის rvalue-ზე რეფერენსის ტიპის
პარამეტრს.
პირიქით, იდეურად ამის მსგავსი std::vector-ის წევრი ფუნქცია emplace_back იყენებს ტიპის
გამოცნობას:
template<class T, class Allocator = allocator<T>> // კვალვ
class vector { // C++-ის
public: // სტანდარტებიდან
template <class... Args>
void emplace_back(Args&&... args);

};
აქ, ტიპის პარამეტრი Args დამოუკიდებელია ვექტორის ტიპის T პარამეტრისგან, ამიტომ Args
უნდა იქნას გამოცნობილი ყოველ ჯერზე, როდესაც ხდება emplace_back-ის გამოძახება
(სინამდვილეში Args არის პარამეტრების კრებული, მაგრამ ამ შემთხვევაში მოვეპყროთ მას
როგორც პარამეტრს).
ის ფაქტი რომ emplace_back-ის ტიპის პარამეტრს ჰქვია Args და მასზე არის უნივერსალური
რეფერენსი, ეწინააღმდეგება ადრე ნათქვამს, რომ უნივერსალური რეფერენსის სახე არის
ზუსტად „T&&“. არ არსებობს რაიმე მოთხოვნა, რომ მხოლოდ სახელი T იყოს გამოყენებული ამ
მიზნით. მაგალითად, შემდეგი თარგში არის უნივერალური რეფერენსი, რადგან სახე (“type&&”)
სწორია და param-ის ტიპი უნდა იქნას გამოცნობილი (კვლავ გამოვრიცხავთ უკიდურეს
შემთხვევას, როდესაც გამომძახებელი ცხადად ახასიათებს ტიპს):
template<typename MyTemplateType> // param არის
void someFunc(MyTemplateType&& param); // უნივერსალური რეფერენსი

ადრე აღნიშნული იყო, რომ auto ცვლადები შესაძლოა აგრეთვე იყვნენ უნივერსალური
რეფერენსები. უფრო ზუსტები რომ ვიყოთ, auto&& ტიპით გამოცხადებული ცვლადები არიან
უნივერსალური რეფერენსები, რადგან ტიპის გამოცნობა ხდება როდესაც მათ აქვთ სწორი
ფორმა („T&&“). auto უნივერსალური რეფერენსები არაა ისეთი გავრცელებული როგორც
ფუნქციის თარგის პარამტრებისთვის გამოყენებულები, მაგრამ ისინი შიგადაშიგ
ამოტივტივდებიან ხოლმე C++11-ში. C++14-ში ისინი ბევრად ხშირად გამოდიან ზედაპირზე,
რადგან C++14-ის ლამბდა გამოსახულებებს შეუძლიათ auto&& პარამეტრების გამოცხადება.

<<< წყაროები:
1. Scott Meyers. Effective Modern C++. O’Reilly, 2014.
2. https://msdn.microsoft.com/en-us/library/f90831hc.aspx
3. http://eli.thegreenplace.net/2011/12/15/understanding-lvalues-and-rvalues-in-c-and-c
4. https://www.ibm.com/support/knowledgecenter/en/SSGH3R_12.1.0/com.ibm.xlcpp121.aix.
doc/language_ref/reference_collapsing.html

Page 8 of 8

You might also like