LINUX.ORG.RU

Негибкость реализаций Go интерфейсов

 ,


0

1

Рассмотрим следующий пример кода на Go:

package main

import "fmt"

type animal interface {
	makeSound()
}

type cat struct {}
type dog struct {}

func (c cat) makeSound() {
	fmt.Println("meow")
}

func (d dog) makeSound() {
	fmt.Println("woof")
}

func main() {
	var cat1, dog1 animal = cat{}, dog{}
	cat1.makeSound()
	dog1.makeSound()

	var cat2, dog2 animal = &cat{}, &dog{}
	cat2.makeSound()
	dog2.makeSound()
}

Этот код работает - программа гавкает и мяукает как по значению, так и по ссылке. Но давайте немного изменим код:

package main

import "fmt"

type animal interface {
	makeSound()
}

type cat struct {}
type dog struct {}

func (c *cat) makeSound() {
	fmt.Println("meow")
}

func (d *dog) makeSound() {
	fmt.Println("woof")
}

func main() {
	var cat1, dog1 animal = cat{}, dog{}
	cat1.makeSound()
	dog1.makeSound()

	var cat2, dog2 animal = &cat{}, &dog{}
	cat2.makeSound()
	dog2.makeSound()
}

Теперь вызовы cat1.makeSound() и dog1.makeSound() не компилируются, выдавая ошибки вроде следующей:

.\test.go:21:6: cannot use cat{} (type cat) as type animal in assignment:
	cat does not implement animal (makeSound method has pointer receiver)

При этом вызовы cat2.makeSound() и dog2.makeSound() продолжают работать.

Почему такое неконсистентное поведение и почему передача того, что в других языках называется this или self в Go не унифицирована? В отличии от других языков программист на Go должен думать не только о сигнатуре метода, но и о способе передачи аналога this или self.

Кстати, объявить одновременно два варианта метода нельзя. Например если написать вот так:

func (c *cat) makeSound() {
	fmt.Println("meow")
}

func (c cat) makeSound() {
	fmt.Println("meow")
}

Будет ошибка:

.\test.go:16:6: method redeclared: cat.makeSound
	method(*cat) func()
	method(cat) func()

Ответ на: комментарий от pru-mike

This rule arises because pointer methods can modify the receiver; invoking them on a value would cause the method to receive a copy of the value, so any modifications would be discarded. The language therefore disallows this mistake. There is a handy exception, though. When the value is addressable, the language takes care of the common case of invoking a pointer method on a value by inserting the address operator automatically.

А что плохого в «any modifications would be discarded», когда речь идёт о возврате из метода? Если я передаю объект по значению, то есть передаю его копию, я должен понимать, что изменения в этой копии не повлияют на исходный объект. Это вовсе не всегда неправильно. Например до возврата из такого метода я могу вызвать что-то ещё и передать туда модифицированную копию объекта, до того, как она discarded после возврата из метода. В этом случае была бы консистентность, а так за меня в Google решили как правильно.

hummer ()

Почему такое неконсистентное поведение и почему передача того, что в других языках называется this или self в Go не унифицирована?

два варианта метода обьявить нельзя, потому что го «методы» записываются с явным обьявлением «селфа» верней это даже не методы, а «связанные» функции, и связь отображается в первых скобочках(это не только в го так, и возникло довольно давно).

а поскольку там может стоять только адрес в том, или ином виде(неужто это не написано в го?) - в виде ссылки или указателя, то обе формы считаются одним и тем же определением.

когда там стоит (this Value), на самом деле это обьявление ссылки, а не копии.

потому трактовать это как создание копии - неверно. если хотите вызывать метод на копии - создайте ее явно и вызывайте

alysnix ()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от hummer

Почему в Google решили за меня как делать нельзя?

потому что спи…ли из Oberon-2

в го небось и передача нескалярного параметра в функцию по значению,на самом деле есть неявная передача ссылки.

alysnix ()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от hummer

А что плохого в «any modifications would be discarded», когда речь идёт о возврате из метода?

Будет плохо, если разработчик случайно забыл где-то * или & написать, а у него из-за этого не ошибка компиляции случилась, а mu.Lock() или buf.Write() в no-op превратился. Такую ошибку можно долго искать.

за меня в Google решили как правильно

Любой ЯП хоть в чём-то решает за пользователя, как правильно.

kmeaw ★★★ ()
Ответ на: комментарий от alysnix

а поскольку там может стоять только адрес в том, или ином виде(неужто это не написано в го?) - в виде ссылки или указателя, то обе формы считаются одним и тем же определением.

когда там стоит (this Value), на самом деле это обьявление ссылки, а не копии.

потому трактовать это как создание копии - неверно. если хотите вызывать метод на копии - создайте ее явно и вызывайте

Да ну? А почему же content не поменялся в orig:

package main

import (
	"fmt"
)

type foo struct {
	content int
}

func (copy foo) setAndGet(x int) int {
	copy.content = x
	return x
}

func main() {
	var orig = foo{content: 42}
	_ = orig.setAndGet(9000)
	fmt.Println(orig.content)
}

=>

42

?

korvin_ ★★★★★ ()
Ответ на: комментарий от korvin_

Да ну? А почему же content не поменялся в orig:

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

вообще это конечно треш. там делается неявная копия просто потому, что это описано в методе.

это что, если написать

v A;
v.method();

то v вообще не поменяется, поскольку изменилась лишь копия? это ж рассадник баг. непонятно зачем они это сделали. такого рода запись в плюсах например обозначает применение метода к v, а не к его копии.

alysnix ()
Последнее исправление: alysnix (всего исправлений: 2)
Ответ на: комментарий от alysnix

это ж рассадник баг. непонятно зачем они это сделали. такого рода запись в плюсах например обозначает применение метода к v, а не к его копии.

Все правильно сделали, пользователь должен иметь возможность настраивать способ передачи this. Это в плюсах чушь какая-то, язык позволяет делать все, что угодно – кроме этого.

потому трактовать это как создание копии - неверно. если хотите вызывать метод на копии - создайте ее явно и вызывайте

А, забыл, вы же у нас специалист по всему.

Siborgium ★★★ ()
Ответ на: комментарий от Siborgium

А, забыл, вы же у нас специалист по всему.

А что такая неприязнь?
И вы профессионал и @alysnix.

… пользователь должен иметь возможность настраивать способ передачи this
Можно пример?

anonymous ()
Ответ на: комментарий от anonymous

А что такая неприязнь?

Никакой неприязни: персонаж (как обычно) начал рассказывать про то, о чем понятия не имеет. Оказался неправ, и теперь пытается объяснить свои ошибки «плохим» дизайном Go.

Можно пример?

Да хоть Rust’овское

fn foo(self) {}  // передает self по значению
fn bar(&self) {} // передает ссылку на self
Siborgium ★★★ ()
Ответ на: комментарий от Siborgium

Все правильно сделали, пользователь должен иметь возможность настраивать способ передачи this.

this, как и self в других языках, это всегда указатель на существующий обьект(или ссылка как self). поскольку это метод существующего обьекта, то передается его адрес в том или ином виде. и пока этого всем хватало.

но гугл решил не останавливаться на достигнутом, и расширить трактовку self, до изготовления копии и передачи ссылки на нее… что они решили этим непонятно.

причем сам способ изготовления копии неясен. или она делается на стеке… это чревато, всякими там переполнениями..ну вот допустим размер обьекта - 10 МБ..и кто-то написал метод с копией сдуру, то будет изготовляться бессмысленная копия гигантских размеров… но если копия делается на куче.. это немногим лучше, поскольку будет кучу фрагментировать.

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

вообще способ передачи по констатной ссылке лучше полюбому. гуглу надо было напрячь мозг и ввести тогда константый поинтер. а не копию.

опять же копирование - дело неоднозначное. в случае наличия в обьекте указателей или ссылок проблема очевидна.

А, забыл, вы же у нас специалист по всему.

я специалист только по здравому смыслу(ЗС), и не признаю «авторитеты» если их рекомендации этому смыслу противоречат. а любое неавторитетное мнение, соответствующее ЗС, только приветствую.

alysnix ()
Ответ на: комментарий от Siborgium

Оказался неправ, и теперь пытается объяснить свои ошибки «плохим» дизайном Go.

мне просто интересно осмысленное обьяснение появления новой и опасной конструкции там, где ничего такого раньше не требовалось.

жили не тужили, и нате вам.

если у вас есть личные соображения, поделитесь. если есть только ссылки на руст… то этого мало.

впрочем одно соображение у меня есть, но я его не скажу. но это частный случай

alysnix ()
Ответ на: комментарий от alysnix

this, как и self в других языках, это всегда указатель на существующий обьект(или ссылка как self). поскольку это метод существующего обьекта, то передается его адрес в том или ином виде. и пока этого всем хватало.

В Go это не объект и «this» всегда означает тот тип, который указан, а не «текущий».

Например, объекты:

interface Printable {
    
    void println();
}

class A implements Printable {

    public void println() {
        System.out.println(this.getClass().getSimpleName());
    }
}

class B extends A { }

public class Objects {

    public static void main(String[] args) {
        Printable object;

        object = new A();
        object.println(); // => A

        object = new B();
        object.println(); // => B
    }
}

структуры Go:

package main

import (
	"fmt"
)

type A struct{}

func (this A) Println() {
	fmt.Printf("%T\n", this)
}

type B struct {
	A
}

type Printable interface {
	Println()
}

func main() {
	var object Printable
	
	object = A{}
	object.Println() // => main.A
	
	object = B{}
	object.Println() // => main.A
}
korvin_ ★★★★★ ()
Ответ на: комментарий от korvin_

В Go это не объект и «this» всегда означает тот тип, который указан, а не «текущий».

эту фразу надо расшифровать, а то непонятно что за «необьект»(обьект у вас это что?) и что такое «текущий тип»??

alysnix ()
Ответ на: комментарий от alysnix

тут у вас копия обьекта делается? вот этим и опасна. зачем для печати обьекта делать его копию?

Во-первых, не надо — не делай. Объяви this как *A

Во-вторых, для гарантии, что метод не изменит состояние объекта (типа как const в C++, только в Go нет такого const, остаётся только копировать)

В-третьих, тут размер «объекта» крайне мал — пустая структура без полей, зачем создавать лишний указатель? Ты же int'ы не передаёшь указателями. Ладно бы там была структура на 100500 полей, другое дело.

korvin_ ★★★★★ ()
Последнее исправление: korvin_ (всего исправлений: 3)
Ответ на: комментарий от korvin_

Во-первых, не надо — не делай. Объяви this как *A

так вы ж ПЕРВЫХ ПАРЕ СТРОК примера и упали в ту самую яму о которой я сказал. вы на АВТОМАТЕ должны были ставить там поинтер, поскольку проблема очевидна.

и рекомендую проверить весь свой код на такие вот копии, поскольку вы явно плодите копии безо всякой нужности, там где они вовсе не нужны.

это стандартная нубская бага в тех же плюсах.

а когда реально нужны копии… когда у обьекта нет адреса. то есть если язык позволяет написать типа - 100.print() или some_const.print() - то есть когда можно привязать «методы» к каким-то скалярам или константам. не знаю можно ли такое в голанге. но это некоего рода извраты конечно

alysnix ()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

так вы ж ПЕРВЫХ ПАРЕ СТРОК примера и упали в ту самую яму о которой я сказал.

Какую яму?

вы на АВТОМАТЕ должны были ставить там поинтер

Не должен был

поскольку проблема очевидна.

Какая проблема?

поскольку вы явно плодите копии безо всякой нужности, там где они вовсе не нужны.

Вовсе нет.

это стандартная нубская бага в тех же плюсах.

В плюсах – возможно.

//— а когда реально нужны копии… когда у обьекта нет адреса. то есть если язык позволяет написать типа - 100.print() или some_const.print() - то есть когда можно привязать «методы» к каким-то скалярам или константам. не знаю можно ли такое в голанге. но это некоего рода извраты конечно

package main

import (
	"fmt"
)

type myIntWrapper int

func (i myIntWrapper) println() {
	fmt.Println(i)
}

const someConst = myIntWrapper(42)

func main() {
	myIntWrapper(100).println()
	someConst.println()
}
korvin_ ★★★★★ ()
Ответ на: комментарий от korvin_

ну вот соббсно о чем я и говорил. копия нужна для привязки к скалярныи типам, навроде инта… но автоматом получать ж. с большими структурными типами - это треш.

Не должен был

должен. когда типа плюсовик пишет функцию, даже просто в примере, вот так

void some_func(std::string fstring)...

это повод для серьезных оргвыводов и проверки его кода.

alysnix ()
Ответ на: комментарий от alysnix

но автоматом получать ж. с большими структурными типами - это треш.

Так внезапно, может, стоит думать, когда пишешь код, не?

должен.

С чего бы? У меня там пустая структура, это ничем не отличается от int, например.

korvin_ ★★★★★ ()
Ответ на: комментарий от alysnix

когда типа плюсовик пишет функцию, даже просто в примере, вот так

void some_func(std::string fstring)...

это повод для серьезных оргвыводов и проверки его кода.

Никто и не говорил, что нужно всё и всегда передавать по-значению с копированием. Но применительно к этому примеру мне знакомый плюсовик вот что ещё написал:

в современных плюсах, если внутри функции надо строку скопировать и куда-то сохранить на память, а не просто по ней пройтись, лучше наоборот принимать по значению.

тогда можно эффективно использовать перемещение как при вызове, так и внутри функции

korvin_ ★★★★★ ()
Ответ на: комментарий от korvin_

в современных плюсах, если внутри функции надо строку скопировать и куда-то сохранить на память, а не просто по ней пройтись, лучше наоборот принимать по значению.

песдешь. он плохой плюсовик. он даже не понимает, что «строка» в плюсах, это структурка, с указателем на буфер в куче. и все данные (то есть сама строка символов) находятся на куче. и «копирование строки» это не копирование буфера на куче, а просто копирование структурки. новая структурка будет указывать на тот же буфер. она хоть и небольшая, но все равно ее копирование дольше чем передача адреса, и адрес будет взят на регистр.

то есть любые манипуляции с буфером на куче, будут одинаковыми, что при «копировании», что при передаче ссылки.

зы. короче не парьте себе мозг, используйте копии только для скаляров типа инта. как я сказал, это специальный случай. во всех остальных используйте указатель. дабы не плодить бестолковые копии, даже случайно

alysnix ()
Последнее исправление: alysnix (всего исправлений: 3)
Ответ на: комментарий от alysnix

песдешь. он плохой плюсовик.

Его мнению я доверяю больше.

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

Что толку от адреса на регистре, если мне в итоге нужно скопировать строку?

korvin_ ★★★★★ ()
Последнее исправление: korvin_ (всего исправлений: 1)
Ответ на: комментарий от korvin_

Зачем мне лишний уровень косвенности, если, например, в случае массива структур, prefetching может дать лучший итоговый результат, чем разыменовывание каждого указателя?

в огороде бузина. копирование структуры от строки будет вам в стек, и вы будете ее оттуда юзать. никто структуру вам регистры не положит. сами же байтики типа «это такая вот строка, траляля» будут на куче, вне зависимости скопировали вы строку эту или брали по ссылке. модифицировать же саму структуру вам особо и не надо, если вы собрались работать с ее байтиками. то есть копирование ее больше времени займет.

и «массив структур» за уши не притягивайте тут. уж такие массивы по ссылке надо передавать. если вы создадите копию такого массива у себя на стеке… вы будете работать опять же с памятью, но в другом месте, потратив время на копирование.

alysnix ()
Ответ на: комментарий от alysnix

он плохой плюсовик. он даже не понимает, что «ст…

Ты плохой плюсовик, пишущий очевидные вещи и считающий себя умнее всех.

Если внутри функции нужно сделать копию строки, действительно лучше передавать по значению. Пусть определены две функции

void copy_by_ref(const std::string & str);
void pass_by_value(std::string str);

Тогда,

std::string lvalue = // ...
copy_by_ref(lvalue);      // 1 копия внутри функции, как и должно быть
copy_by_ref("temporary"); // 2 копии: в временную строку и внутри функции
std::string lvalue = // ...
pass_by_value(lvalue);      // 1 копия 
pass_by_value("temporary"); // 1 копия

зы. короче не парьте себе мозг, и

Короче ты опять сел в лужу и пытаешься побыстрее отвлечь от этого внимание окружающих.

дабы не плодить бестолковые копии, даже случайно

Еще раз, копирование в ряде случаев дешевле работы с указателем. Мне – не тебе – виднее, когда мне нужно копировать, а когда нужно передавать по указателю.

Siborgium ★★★ ()
Ответ на: комментарий от anonymous

&self является аналогом *&? …

Не распарсил. &self – это аналог this, только ссылка, а не указатель.

то зачем и в чем профит?

Профит в том, что явно передается владение. Напоминаю, речь идет о расте.

Siborgium ★★★ ()
Последнее исправление: Siborgium (всего исправлений: 1)
Ответ на: комментарий от Siborgium

Еще раз, копирование в ряде случаев дешевле работы с указателем.

У меня для CHAR и TCHAR по функции, которые создают копию строки и возвращают адрес результата.

Все!

ИМХО API типа std::string, CString, … это

Абы что ...  

Впрочем с учетом того, что нынешнее железо весьма производительно,
любые СТУДЕНЧЕСКИЕ лабы работают шустро …

anonymous ()
Ответ на: комментарий от korvin_

В-третьих, тут размер «объекта» крайне мал — пустая структура без полей, зачем создавать лишний указатель? Ты же int’ы не передаёшь указателями. Ладно бы там была структура на 100500 полей, другое дело.

Вот другой пример кода, с полями:

package main

import "fmt"

type animal interface {
	makeSound()
}

type cat struct {
	scratching bool
}

type dog struct {
	biting bool
}

func (c cat) makeSound() {
	fmt.Printf("meow from %p\n", &c)
}

func (d dog) makeSound() {
	fmt.Printf("woof from %p\n", &d)
}

func main() {
	var cat1, dog1 animal = cat{}, dog{}

	fmt.Printf("Using cat from %p and dog from %p\n", &cat1, &dog1)
	cat1.makeSound()
	dog1.makeSound()

	var cat2, dog2 animal = &cat{}, &dog{}

	fmt.Printf("Using cat from %p and dog from %p\n", cat2, dog2)
	cat2.makeSound()
	dog2.makeSound()
}

Напечатает что-то типа:

Using cat from 0xc000042230 and dog from 0xc000042240
meow from 0xc00000a09a
woof from 0xc00000a09b
Using cat from 0xc00000a09c and dog from 0xc00000a09d
meow from 0xc00000a09e
woof from 0xc00000a09f

А если поля закоментировать, то есть вернуться к прежним пустым структурам, то адреса кошки и собаки неожиданно станут почти всегда одинаковыми:

Using cat from 0xc000042230 and dog from 0xc000042240
meow from 0xbb82f8
woof from 0xbb82f8
Using cat from 0xbb82f8 and dog from 0xbb82f8
meow from 0xbb82f8
woof from 0xbb82f8

Похоже на какую-то оптимизацию.

hummer ()
Последнее исправление: hummer (всего исправлений: 1)
Ответ на: комментарий от Siborgium

Если внутри функции нужно сделать копию строки,

люблю таких людей… я рассказываю, что делать копию нехорошо, там где ее вовсе НЕ НАДО… НЕ НАДО КОПИЮ, вы понимаете?, это большими буквами написано.

товарищ приходит, и на голубом глазу говорит - если внутри функции НАДО СДЕЛАТЬ КОПИЮ строки…и начинает бурные рассуждения. и считает себя умным и положительным товарищем.

причем у товарища интерфейс функции зависит от внутренней реализации. если там требовалась копия, то он написал интерейс с копией(типа для скорости в частном случае констант)… а если вдруг копия требоваться перестала(просто пришел другой товарищ и написал более грамотную рализацию)… то интерфейс с копией уже плох и стал работать против скорости.

уже за одну зависимость интерфейса от реализации надо пороть, поскольку второе меняется в любую минуту, а первое желательно не менять никогда.

даже странно, что товарищ не понимает такие банальности, которые и приходится ему излагать. а потом он же и обвиняет вас в банальностях.

alysnix ()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от Siborgium
std::string lvalue = // ...
pass_by_value(lvalue);      // 1 копия 
pass_by_value("temporary"); // 1 копия

дорогой сиборгиум. откуда вообще взято что в случае 2 - одна копия?

уважающие себя компиляторы(все зависимости от языка), копии структурных(то есть нескалярных) параметров делают так.

псеводкод

MyStructure = class {
  f0:int;
  f1:int;
...
}

и функция вида 
  ff(param: MyStructure){...}

на самом деле компилируется в функцию вида
  ff(param: const ref MyStructure){
    hidden_local_copy MyStructure(param);
    ...
  }

то есть в реальности в фукцию передается ссылка, а не весь обьект неким образом. а в прологе функции есть код создающий локальную копию, с которой и делается вся работа потом.

тогда в случае строковой константы и передачи по значению у вас будет создан в вызывающем коде локальный обьект типа string, затем ваша функция pass_by_value (хихи), будет вызвана с его адресом. потом она в прологе сделает на стеке себе копию… и получается..вах.. две копии(ну или два обьекта), а не один.

то есть циничная замена компилятором вашей «подстановки по значению» подстановкой по ссылке и копированием изнутри вызываемой функции, делает функции - by_ref и by_copy идентичными в случае нескалярных типов

alysnix ()
Последнее исправление: alysnix (всего исправлений: 2)
Ответ на: комментарий от alysnix

а если вдруг копия требоваться перестала

А если острым, и в глаз?

тогда в случае строковой константы и передачи по значению у вас будет создан в вызывающем коде локальный обьект типа string, затем ваша функция pass_by_value (хихи), будет вызвана с его адресом. потом она в прологе сделает на стеке себе копию… и получается..вах.. две копии(ну или два обьекта), а не один.

Советую попробовать читать. Прекращайте позориться, посмотрите хотя бы godbolt.

Siborgium ★★★ ()
Ответ на: комментарий от Siborgium

Советую попробовать читать. Прекращайте позориться, посмотрите хотя бы godbolt.

советую начинать ориентироваться в окружающем пространстве и топике в частности. вы написали пример, лютейше неадекватный обсуждаемому вопросу.

мы обсуждаем передачу в функцию структурного типа!!! по значениию и по ссылке. где мол и как это будет копироваться.

вы пишете структурный тип, без единого поля!!!. то есть копировать там просто нечего. и пишете функции передачи типа нулевой длины по ссылке и значению.

затем. вы пишете функцию куда передается по значению обьект нулевой длины… и о чудо! она оказывается совсем короткой. ну правильно. вы в функцию передали только адрес источника, длина ноль, ничто не копируется. код короткий. или вы думаете компилятор вам сгенерит код для копирования нуля байт?

потрудитесь ввести хотя б 4 int поля в свой обьект, чтобы было хоть что копировать.

дальше. даже если вы добавите поля, ваша функция, что берет по значению структуру, ничего с ней не делает, то есть функция у вас пустая. в таком случае компилятор вряд ли станет генерить код взятия копии, поскольку с этой нет вообще никакой работы и соптимизирует его. сделайте хоть что-то с копией то!

в микроскопический пример вы засадили 2 принципиальные баги короче.

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

и вот когда вы все это до ума доведете, увидите что я прав.

alysnix ()
Последнее исправление: alysnix (всего исправлений: 2)
Ответ на: комментарий от Siborgium

Прекращайте позориться,

про позориться :)))

если чо, пример ваш такой(ну чтобы нотариально заверить)

#include <iostream>

struct string_like {
    string_like() = default;
    string_like(const string_like&) {
        std::cout << __PRETTY_FUNCTION__ << '\n';
    }
    string_like(string_like &&) {
        std::cout << __PRETTY_FUNCTION__ << '\n';
    }
    string_like(const char*) {
        std::cout << __PRETTY_FUNCTION__ << '\n';
    }
};

void pass_by_value(string_like s) {
    // ...
}

void pass_by_ref(const string_like & s) {
    string_like t{ s };
}

int main() {
    string_like lvalue;
    std::cout << "[by ref lvalue]\n";
    pass_by_ref(lvalue);
    std::cout << "[by ref temporary]\n";
    pass_by_ref("temporary");
    std::cout << "[by value lvalue]\n";
    pass_by_value(lvalue);
    std::cout << "[by value temporary]\n";
    pass_by_value("temporary");
}

в структуре string_like нет ни одного поля. :))) свежая методика изучения копирования на длине 0.

функция pass_by_value - пустая. копирование не делается сразу по двум причинам. и размер ноль и пустота функции.

alysnix ()