Претоварване на const версия на [] оператор за шаблонен клас

Още един въпрос относно претоварването на оператор [] в C++, по-специално неговата const версия.

Според cppreference страница за претоварване на оператор, при претоварване на долен оператор на масив

struct T
{
          value_t& operator[](std::size_t idx)       { return mVector[idx]; }
    const value_t& operator[](std::size_t idx) const { return mVector[idx]; }
};

Ако е известно, че типът стойност е вграден тип, вариантът const трябва да върне по стойност.

Така че, ако value_t случайно е вграден тип, вариантът const трябва да изглежда

    const value_t operator[](std::size_t idx) const { return mVector[idx]; }

или вероятно дори

   value_t operator[](std::size_t idx) const { return mVector[idx]; }

тъй като квалификаторът const не е много полезен при такава върната стойност.


Сега имам шаблонен class T (за да запазя същото именуване като при препратката), който се използва и както с вградени типове данни, така и с дефинирани от потребителя, някои от които може да са тежки.

template<class VT>
struct T
{
          VT& operator[](std::size_t idx)       { return mVector[idx]; }
    const VT& operator[](std::size_t idx) const { return mVector[idx]; }
};

Съгласно дадения съвет по-горе, трябва да използвам enable_if с някои type_traits, за да разгранича между шаблонни инстанции на класове с вградени/невградени типове.

трябва ли да го правя Тази препоръка само ли е, за да се избегне потенциално ненужно дерефериране за вградени типове или нещо друго се крие зад нея, за което човек трябва да знае?


Бележки:

  • този клас активно участва в гореща част от кода, създаден както с вградени, така и с персонализирани типове.
  • кодът се използва между платформи с множество компилатори с различни степени на опции за оптимизация.
  • по този начин се интересувам да го направя както правилен, така и преносим, ​​както и да избегна всякакво потенциално увреждане на производителността.
  • Не можах да намеря никакви допълнителни разсъждения в стандарта C++, но четенето на standardeze не е най-силната ми страна.

Съществуващи въпроси относно StackOverflow:


person Anton Menshov    schedule 17.12.2019    source източник
comment
Не виждам причина да не се връща винаги по препратка. Защо основният value_t трябва да се държи по различен начин?   -  person walnut    schedule 17.12.2019
comment
@walnut потенциално ненужно дерефериране за вградени типове, може би (?) предотвратяване на избягване на копиране и два уважавани източника препоръчват друго. -› И така, въпросът ми, тъй като се чудя дали всъщност трябва да правя разлика между вградени и невградени типове, използвайки enable_if   -  person Anton Menshov    schedule 17.12.2019


Отговори (2)


Не съм съгласен с горния "съвет". Помислете за това:

T t = /*Initialize `t`*/;
const T::value_t &vr = std::as_const(t)[0];
const auto test = vr; //Copy the value
t[0] = /*some value other than the original one.*/
assert(test != vr);

Твърдението задейства ли се? Не трябва да се задейства, защото ние просто препращаме към стойността в контейнера. По принцип std::as_const(t)[i] трябва да има същия ефект като std::as_const(t[i]). Но не става, ако вашата const версия връща стойност. Така че извършването на такава промяна фундаментално променя семантиката на кода.

Така че дори ако знаете value_t е основен тип, пак трябва да върнете const&.

Обърнете внимание, че C++20 диапазоните официално признават диапазони, които не връщат действителни value_type&s от техните operator* или еквивалентни функции. Но дори и тогава такива неща са основна част от естеството на този диапазон, а не са свойство, което се променя въз основа на параметъра на шаблона (вижте vector<bool> за причините, поради които това е лоша идея).

person Nicol Bolas    schedule 17.12.2019

Не е необходимо да обработвате основни типове по специален начин. Просто винаги връщайте value_t& за не-const варианта и const value_t& за const варианта.

Претоварванията обикновено са кратки, както във вашите примери, така че така или иначе ще бъдат вградени във всеки сайт за повикване. В този случай няма значение дали претоварването връща по стойност или по препратка, и в двата случая косвеността ще бъде оптимизирана. Всеки донякъде модерен компилатор, настроен на поне ниско ниво на оптимизация, трябва да направи това.

Няма друга причина да се борави с основните типове по различен начин, за която мога да се сетя.


Също така бих предупредил, че ако сте напр. внедряването на контейнерен клас, както във вашите примери, връщането на препратка и връщането на стойност ще имат различна семантика за потребителя.

Ако върнете препратка const към елемент, потребителят може да запази тази препратка и да наблюдава промените в елемента на контейнера (докато невалидността на препратката се случи по определен начин). Ако върнете стойност, е невъзможно да наблюдавате промените.

Би било изненадващо за потребителя, ако може да получи справка и да наблюдава бъдещи промени за някои типове, но не и за други. В най-лошия случай, ако използват и типов код, те също ще трябва да кондиционират всички свои шаблони.

Също така, дори ако върнете по стойност за основни типове, претоварването, връщащо по стойност, ще бъде извикано само чрез const препратки към обекта. В повечето случаи потребителят вероятно има не-const екземпляр на вашия контейнер и за да се възползва от тази потенциална оптимизация, той ще трябва изрично да прехвърли препратката към обекта си към const първо, преди да извика претоварването на оператора.

Така че, ако оптимизацията е проблем, бих предпочел да добавя допълнителна членска функция, която винаги връща копие на елемента на контейнера по стойност и която може да се използва от потребителя, ако потенциалното дерефериране бъде идентифицирано като проблем с производителността . Извикването на тази членска функция няма да бъде по-голям проблем от това да се уверите, че извиквате правилното претоварване на оператора.

person walnut    schedule 17.12.2019