Почему предложение «Выражение можно использовать только как левый операнд вызова функции-члена» в [expr.ref] p (6.3.2)? - PullRequest
3 голосов
/ 01 июня 2019

[expr.ref] p (6.3.2) :

В противном случае, если E1.E2 относится к нестатической функции-члену и типу E2 является «функцией списка параметров-типов» cv квалификатор ref opt возвращающий T », тогда E1.E2 является prvalue,Выражение обозначает нестатическую функцию-член. Выражение можно использовать только как левый операнд вызова функции-члена ([class.mfct]) .[Примечание: любой избыточный набор скобок, окружающих выражение, игнорируется ([expr.prim.paren]).- примечание конца] Тип E1.E2 - это «функция списка параметров-типов cv , возвращающая T».

Например, второй оператор в mainниже не компилируется, вероятно, из-за выделенного предложения выше.Но почему язык настроен так?

#include<iostream>
void g();
struct S { void f(); };
S s;

int main(){
    std::cout << "decltype(g) == void() ? " << std::is_same<decltype(g), void()>::value << '\n';            // Ok
    std::cout << "decltype(s.f) == void() ? " << std::is_same<decltype(s.f), void()>::value << '\n';        // Doesn't compile probably because of the sentence hihlighted above in [expr.ref]p(6.3.2).
}

Ответы [ 2 ]

3 голосов
/ 01 июня 2019

Когда вы делаете E1.E2, вы не говорите об общем свойстве того типа вещи, которым является E1. Вы просите получить доступ к объекту в объекте , обозначенном E1, где имя объекта, к которому нужно получить доступ, - E2. Если E2 статический, он обращается к классу static; если E2 не является статичным, то он получает доступ к элементу , специфичному для этого объекта . Это важно.

Переменные-члены становятся подобъектами. Если в вашем классе S был элемент нестатических данных int i;, s.i является ссылкой на int. Эта ссылка, с точки зрения int&, ведет себя не иначе, как любая другая int&.

Позвольте мне сказать это более четко: любой int* или int& может указывать / ссылаться на int, который является законченным объектом, или int, который является подобъектом какого-либо другого объекта. Таким образом, одиночная конструкция int& может выполнять двойную функцию. *

Учитывая это понимание s.i, каково будет предполагаемое значение s.f? Ну, это должно быть похоже, верно? s.f будет чем-то вроде того, что при вызове с params будет эквивалентно выполнению s.f(params).

Но это не та вещь, которая существует в C ++.

В C ++ нет языковой конструкции, которая могла бы представлять значение s.f. Такая конструкция должна хранить ссылку на s, а также член S::f.

Указатель функции не может этого сделать. Функциональные указатели должны иметь возможность взаимного преобразования указателей с void***. Но такой s.f должен хранить элемент S::f, а также ссылку на сам s. Таким образом, по определению, он должен быть больше, чем void*.

Указатель члена тоже не может этого сделать. Указатели-члены явно не несут с собой свой объект this (в этом вся суть); Вы должны предоставить их во время вызова, используя специальный синтаксис вызова указателя члена .* или .->.

О, есть способы закодировать это в языке: лямбды, std::bind и т. Д. Но не существует конструкции на уровне языка, которая бы имела такое точное значение.

Поскольку C ++ таким образом асимметричен, где s.i имеет кодируемое значение, но не s.f, C ++ делает некодируемое значение недопустимым.

Вы можете спросить, почему такая конструкция не просто создается. Это не так важно. Язык работает отлично, как есть, и из-за сложности того, чем должен быть s.f, вероятно, лучше всего заставить вас использовать лямбду (для которой, по общему признанию, должны быть способы сделать ее короче, чтобы писать такие вещи) если ты этого хочешь.

И если вы хотите, чтобы обнаженная s.f была эквивалентна S::f (т.е. обозначает функцию-член), это тоже не работает. Во-первых, S::f также не имеет типа; единственное, что вы можете сделать с таким значением, это преобразовать его в указатель на член. Во-вторых, указатель на функцию-член не знает, из какого объекта он пришел, поэтому, чтобы использовать его для вызова члена, вам нужно дать ему s. Следовательно, в выражении вызова s должно появиться дважды. Что действительно глупо.

*: есть вещи, которые вы можете делать , чтобы завершить объекты, которые вы не можете сделать с подобъектами. Но они провоцируют UB, потому что они не обнаруживаются компилятором, потому что int* не говорит, идет он от подобъекта или нет. Что является основным моментом; никто не может отличить.

**: стандарт не требует этого, но стандарт не может сделать то, что прямо делает такую ​​реализацию невозможной . Большинство реализаций предоставляют эту функциональность, и в основном любой загрузочный код DLL / SO полагается на него. О, и это также будет полностью несовместимо с C, что делает его не стартером.

1 голос
/ 02 июня 2019

Синтаксис stat_result.st_mtime был унаследован от C, где он всегда имеет значение (в частности, lvalue).Следовательно, синтаксически является выражением даже в случае вызова метода, но не имеет значения, поскольку оно оценивается совместно со связанным с ним выражением вызова.

Это (как вы цитировали)) с учетом типа функции-члена, чтобы удовлетворить требование , чтобы функция вызывалась через выражение правильного типа.Однако было бы вводящим в заблуждение для определения decltype для него, поскольку оно не может быть полным выражением (как для каждого неоцененного операнда ) и общим выражениемSFINAE с decltype не предотвратит серьезную ошибку из защищенного экземпляра.

Обратите внимание, что нестатическая функция-член может быть названа в в неоцененном операнде,но это позволяет S::f (или просто f внутри класса, хотя это, вероятно, переписано , чтобы быть (*this).f в функции-члене), а не s.f.Это выражение имеет тот же тип , но само по себе ограничено (появляется с & для формирования указателя на член [function]) по той же причине: если бы оно использовалось иначе, его можно было бы использоватькак обычный указатель [function], что невозможно.

...