Проблема
Предположим, что мы реализуем класс string
, который представляет, uhm, строки. Затем мы хотим добавить operator+
, который объединяет два string
s и решает реализовать это с помощью шаблонов выражений, чтобы избежать множественных распределений при выполнении str1 + str2 + ... + strN
.
Оператор будет выглядеть так:
stringbuilder<string, string> operator+(const string &a, const string &b)
stringbuilder
- это класс шаблонов, который, в свою очередь, перегружает operator+
и имеет неявный оператор преобразования string
. Практически стандартный учебник:
template<class T, class U> class stringbuilder;
template<> class stringbuilder<string, string> {
stringbuilder(const string &a, const string &b) : a(a), b(b) {};
const string &a;
const string &b;
operator string() const;
// ...
}
// recursive case similar,
// building a stringbuilder<stringbuilder<...>, string>
Вышеупомянутая реализация работает отлично, если кто-то делает
string result = str1 + str2 + ... + strN;
Однако имеет тонкую ошибку. Присвоение результата переменной правильного типа приведет к тому, что эта переменная будет содержать ссылки на все строки, которые составляют выражение. Это означает, что, например, изменение одной из строк приведет к изменению результата:
void print(string);
string str1 = "foo";
string str2 = "bar";
right_type result = str1 + str2;
str1 = "fie";
print(result);
Это напечатает fiebar, из-за ссылки str1, хранящейся внутри шаблона выражения. Ухудшается:
string f();
right_type result = str1 + f();
print(result); // kaboom
Теперь шаблон выражения будет содержать ссылку на разрушенное значение, сразу же разбив вашу программу.
Теперь что это за right_type
? Это, конечно, stringbuilder<stringbuilder<...>, string>
, т.е. Тип, создаваемый магией шаблона выражения для нас.
Теперь зачем использовать скрытый тип? Фактически, он не использует его явно - , но С++ 11 автоматически делает!
auto result = str1 + str2 + ... + strN; // guess what going on here?
Вопрос
Суть в том, что этот способ реализации шаблонов выражений (путем хранения дешевых ссылок вместо копирования значений или с использованием общих указателей) прерывается, как только один пытается сохранить шаблон выражения сам.
Поэтому я бы очень хотел, чтобы метод обнаруживал, что я строю rvalue или lvalue, а предоставляет различные варианты реализации шаблона выражения в зависимости от независимо от того, построено ли значение rvalue (сохранить ссылки) или lvalue (сделать копии).
Существует ли установленный шаблон проектирования для обработки этой ситуации?
Единственное, что мне удалось выяснить во время моих исследований, было то, что
-
Можно перегрузить функции-члены в зависимости от
this
как lvalue или rvalue, т.е.class C { void f() &; void f() &&; // called on temporaries }
однако, похоже, я тоже не могу это сделать на конструкторах.
-
В С++ нельзя действительно "перегружать типы", т.е. предлагать несколько реализаций одного и того же типа, в зависимости от того, как тип будет использоваться (экземпляры, созданные как lvalues или rvalues).