تمكّن الكلمة المفتاحية auto
من الاستنتاج التلقائي لنوع متغيّر معيّن، وهي مناسبة بشكل خاص عند التعامل مع أسماء الأنواع الطويلة:
std::map < std::string, std::shared_ptr < Widget > > table; // C++98 std::map < std::string, std::shared_ptr < Widget > > ::iterator i = table.find("42"); // C++11/14/17 auto j = table.find("42");
يمكن استخدامها مع حلقات for
النطاقية:
vector v = {0, 1, 2, 3, 4, 5}; for (auto n: v) std::cout << n << ' ';
أو مع تعبيرات لامدا:
auto f = []() { std::cout << "lambda\n"; }; f();
أو يمكن استخدامها لتجنّب تكرار النوع:
auto w = std::make_shared < Widget > ();
أو لتجنّب عمليّات النسخ غير الضرورية:
auto myMap = std::map<int,float>(); myMap.emplace(1,3.14); std::pair<int,float> const& firstPair2 = *myMap.begin(); // ! نسخ auto const& firstPair = *myMap.begin(); // !لا تنسخ
سبب إجراء عمليّة النسخ في الشيفرة أعلاه يعود إلى أنّ النوع المُعاد هو std::pair<const int,float>
!
تعبيرات لامدا العامة (C++ 14)
الإصدار ≥ C++ 14
تسمح C++ 14 باستخدام الكلمة المفتاحية auto
في وسيط لامدا.
auto print = [](const auto& arg) { std::cout << arg << std::endl; }; print(42); print("hello world");
ويكافئ تعبير لامدا في الغالب الشيفرة التالية:
struct lambda { template < typename T > auto operator ()(const T& arg) const { std::cout << arg << std::endl; } };
ثمّ:
lambda print; print(42); print("hello world");
كائنات auto و proxy
في بعض الأحيان، قد لا تتصرّف الكلمة المفتاحية auto
كما هو متوقّع، فقد تسعى إلى استنتاج نوع التعبير حتى عندما لا يكون استنتاج النوع مطلوبًا، كما هو الحال عند استخدام الكائنات الوكيلة (proxy objects) في الشيفرة:
std::vector flags{true, true, false}; auto flag = flags[0]; flags.push_back(true);
في الشيفرة أعلاه، ليست flag
من النوع bool
، بل من النوع std::vector<bool>::reference
، ففي تخصيص النوع bool
للقالب vector
، يعيد المعامل operator []
كائنًا وكيلًا مع مُعامل التحويل operator bool
المُحدَّد.
وعندما يعدّل التابع flags.push_back(true)
الحاويةَ، فقد يصبح المرجع مُعلّقًا (dangling)، أي يشير إلى عنصر لم يعد موجودًا. كما أنّه سيجعل الموقف التالي ممكنًا:
void foo(bool b); std::vector < bool > getFlags(); auto flag = getFlags()[5]; foo(flag);
تُتجَاهل vector
على الفور، لذلك ستكون flag
مرجعًا زائفًا (pseudo-reference) يشير إلى عنصر تمّ تجاهله. وسيؤدّي استدعاء foo
إلى سلوك غير محدّد.
يمكنك التصريح في مثل هذه الحالات عن متغيّر باستخدام auto
، ثمّ تهيئته عبر تحويله إلى النوع الذي تريد استنتاجه:
auto flag = static_cast < bool > (getFlags()[5]);
لكن قد يكون من الأفضل إحلال bool
مكان auto
.
إحدى الحالات الأخرى التي قد تتسبّب الكائنات الوكيلة فيها بمشاكل هي قوالب التعبير (expression templates)، ففي هذه الحالة لا تكون القوالب مُصمّمة لتستمر إلى ما بعد التعبير الكامل الحالي، وذلك لأجل تحسين الكفاءة، لذا فإنّ استخدام الكائن الوكيل في هذه الحالة قد يؤدّي إلى سلوك غير معرَّف.
auto و قوالب التعبير
يمكن أن تتسبب auto
بمشاكل في حال استخدامها مع قوالب التعبير:
auto mult(int c) { return c * std::valarray{1}; } auto v = mult(3); std::cout << v[0];
والسبب في ذلك هو أنّ استخدام المعامل operator*
على valarray
سيمنحك كائنًا وكيلًا يشير إلى valarray
، وذلك كوسيلة للتقييم المُرجَأ (lazy evaluation). كذلك فإن استخدام auto
سيؤدّي إلى إنشِاء مرجع مُعلّق (dangling reference)، وسيُعاد النوع std::valarray<int>
بدلًا من mult
، وتطبع الشيفرة القيمة 3.
auto و const والمراجع
تمثل الكلمة المفتاحية auto
بحدّ ذاتها نوعًا من القيم، مثل int
أو char
، ويمكن تعديلها باستخدام الكلمة المفتاحية const
والرمز &
لتمثيل نوع ثابت أو نوع مرجعي (reference type) على التوالي، كما يمكن دمج هذه المعدِّلات معًا.
تمثل تمثّل s
في هذا المثال، نوع قيمة - value type - (سيُستنتَج نوعها بأنّه سلسلة نصّية std::string
)، ومن ثم فإنّ كل تكرار للحلقة for
سَينسخ سلسلة نصية من [المتجهة](رابط الفصل 49) إلى s
.
std::vector < std::string > strings = { "stuff", "things", "misc" }; for (auto s: strings) { std::cout << s << std::endl; }
إذا عدَّل جسمُ حلقة التكرار العنصر s
(مثلًا عبر استدعاء s.append(" and stuff")
)، فلن تُعدَّل إلّا هذه النسخة فقط، وليس العضو الأصلي في المتجهة strings
.
من ناحية أخرى، فإن صُرِّح عن السلسلة النصّية s
عبر auto&
، فستكون نوعًا مرجعيًا - reference type - (يُستنتج على أنّه std::string&
)، لذا، ففي كلّ تكرار للحلقة سيُسند إليها مرجع يشير إلى سلسلة نصية في المتجهة:
for(auto& s : strings) { std::cout << s << std::endl; }
ستؤثّر التعديلات على s
في جسم هذه الحلقة مباشرةً على العنصر الذي تشير إليه من المتجهة strings
. وأخيرًا، فإن صُرِِّح عنs
عبر const auto&
، فستكون نوعًا مرجعيًا ثابتًا (const reference type)، ممّا يعني أنّه في كل تكرار من الحلقة، سيُسنَد إليها مرجع ثابت يشير إلى سلسلة نصّية في المتجهة:
for (const auto& s: strings) { std::cout << s << std::endl; }
لا يمكن تعديل s
(أي لا يمكن استدعاء توابع غير ثابتة عليها) داخل جسم هذه الحلقة. كذلك يُستحسن عند استخدام auto
مع حلقات for
النطاقية، أن تُستخدم const auto&
إذا كان متن الحلقة لن يعدّلَ البنية التي يُجري عليها التكرار، لأنّ هذا سيُجنّبك عمليات النسخ غير الضرورية.
نوع الإعادة الزائد Trailing return type
تُستخدَم auto
في صياغة النوع المُعاد الزائد:
auto main() -> int {}
تكافئ الشيفرة أعلاه:
int main() {}
يمكن استعمال auto
مع decltype
لاستخدام المُعاملات بدلًا من std::declval<T>
:
template <typename T1, typename T2> auto Add(const T1& lhs, const T2& rhs) -> decltype(lhs + rhs) { return lhs + rhs; }
هذا الدرس جزء من سلسلة دروس عن C++.
ترجمة -بتصرّف- للفصل Chapter 108: auto من كتاب C++ Notes for Professionals
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.