اذهب إلى المحتوى

الاستنتاج التلقائي لنوع متغير عبر auto في Cpp


محمد بغات

تمكّن الكلمة المفتاحية ‎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


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...