В разделе « Операции, подобные STL» Бьярна Страуструпа The C ++ Programming Language 4th edition, в качестве примера цепочки используется следующий код :36.3.6
void f2()
{
std::string s = "but I have heard it works even if you don't believe in it" ;
s.replace(0, 4, "" ).replace( s.find( "even" ), 4, "only" )
.replace( s.find( " don't" ), 6, "" );
assert( s == "I have heard it works only if you believe in it" ) ;
}
Утверждение терпит неудачу gcc
( смотрите вживую ) и Visual Studio
( смотрите вживую ), но оно не терпит неудачу при использовании Clang ( смотрите вживую ).
Почему я получаю разные результаты? Может ли какой-либо из этих компиляторов неправильно оценивать выражение цепочки или этот код демонстрирует некоторую форму неопределенного или неопределенного поведения ?
s.replace( s.replace( s.replace(0, 4, "" ).find( "even" ), 4, "only" ).find( " don't" ), 6, "" );
cout << a << b << c
≡operator<<(operator<<(operator<<(cout, a), b), c)
лишь немного менее уродливо.Ответы:
Код демонстрирует неопределенное поведение из-за неопределенного порядка оценки подвыражений, хотя он не вызывает неопределенное поведение, поскольку все побочные эффекты выполняются внутри функций, что в данном случае вводит взаимосвязь между побочными эффектами.
Этот пример упоминается в предложении N4228: Уточнение порядка оценки выражений для идиоматического C ++, в котором говорится следующее о коде в вопросе:
подробности
Многим может быть очевидно, что аргументы функций имеют неопределенный порядок оценки, но, вероятно, не так очевидно, как это поведение взаимодействует с вызовами связанных функций. Когда я впервые проанализировал этот случай, это было неочевидно для меня, и, видимо, не для всех экспертов-рецензентов .
На первый взгляд может показаться, что, поскольку каждая из
replace
них должна оцениваться слева направо, соответствующие группы аргументов функций также должны оцениваться как группы слева направо.Это неверно, аргументы функции имеют неопределенный порядок оценки, хотя цепочка вызовов функций действительно вводит порядок оценки слева направо для каждого вызова функции, аргументы каждого вызова функции только упорядочиваются до того, как вызов функции-члена они являются частью оф. В частности, это касается следующих вызовов:
и:
которые имеют неопределенную последовательность в отношении:
два
find
вызова могут быть оценены до или послеreplace
, что имеет значение, так как он имеет побочный эффект,s
который может изменить результатfind
, он изменяет длинуs
. Так что в зависимости от того, когда этоreplace
оценивается относительно двухfind
вызовов, результат будет отличаться.Если мы посмотрим на выражение цепочки и исследуем порядок оценки некоторых подвыражений:
и:
Обратите внимание, мы игнорируем тот факт, что
4
и7
может быть разбито на большее количество подвыражений. Так:A
упорядочивается до того,B
что идет доC
которое секвенируется передD
1
к9
имеют неопределенную последовательность по отношению к другим подвыражениям с некоторыми исключениями, перечисленными ниже1
к3
последовательность передB
4
в6
последовательность передC
7
в9
последовательность передD
Ключ к этой проблеме в том, что:
4
в9
неопределенно последовательность относительноB
Возможный порядок выбора оценки для
4
и в7
отношенииB
объясняет разницу в результатах между оценкойclang
иgcc
при оценкеf2()
. В моих тестахclang
оцениваетB
перед оценкой4
и в7
то время какgcc
оценивает его после. Мы можем использовать следующую тестовую программу, чтобы продемонстрировать, что происходит в каждом случае:Результат для
gcc
( посмотреть вживую )Результат для
clang
( посмотреть вживую ):Результат для
Visual Studio
( посмотреть вживую ):Детали из стандарта
Мы знаем, что, если не указано иное, вычисления подвыражений не упорядочены, это из чернового стандартного раздела C ++ 11 «
1.9
Выполнение программы», в котором говорится:и мы знаем, что вызов функции вводит последовательную связь перед вызовом функции постфиксным выражением и аргументами по отношению к телу функции из раздела
1.9
:Мы также знаем, что доступ к членам класса и, следовательно, цепочка будут оцениваться слева направо из раздела
5.2.5
Доступ к членам класса, в котором говорится:Обратите внимание, что в случае, когда id-выражение оказывается нестатической функцией-членом, оно не определяет порядок оценки списка выражений внутри,
()
поскольку это отдельное подвыражение. Соответствующая грамматика из5.2
выражений Postfix :Изменения в C ++ 17
Предложение p0145r3: Уточнение порядка оценки выражений для идиоматического C ++ внесло несколько изменений. Включая изменения, которые придают коду четко заданное поведение за счет усиления порядка правил оценки для постфиксных выражений и их списка выражений .
[expr.call] p5 говорит:
источник
"even"
,"don't"
и несколько случаевs
являются unsequenced по отношению друг к другу.foo().func( bar() )
, он мог позвонитьfoo()
как до, так и после звонкаbar()
. Постфикс-выражение являетсяfoo().func
. Аргументы и постфиксное выражение располагаются перед теломfunc()
, но не по порядку относительно друг друга.Это предназначено для добавления информации по этому вопросу в отношении C ++ 17. Предложение ( Порядок уточнения выражения для идиоматической версии C ++ Revision 2 ) для
C++17
решения проблемы со ссылкой на приведенный выше код было в качестве образца.Как было предложено, я добавил соответствующую информацию из предложения и процитировал (выделил мое):
В документе предлагается изменить предварительное
C++17
правило по порядку оценки выражений, которое существует под влияниемC
и существует уже более трех десятилетий. Было предложено, чтобы язык гарантировал современные идиомы или рисковал «ловушками и источниками непонятных, труднообнаруживаемых ошибок». таких как то, что произошло с примером кода выше.Предложение
C++17
состоит в том, чтобы требовать, чтобы каждое выражение имело четко определенный порядок оценки :Приведенный выше код успешно компилируется с использованием
GCC 7.1.1
иClang 4.0.0
.источник