Подтвердить что ты не робот

Нестационарный член как аргумент по умолчанию для функции нестатического члена

struct X
{
   X():mem(42){}
   void f(int param = mem) //ERROR
   {
      //do something
   }
private: 
   int mem;
};

Может ли кто-нибудь дать мне только одну причину, почему это незаконно в С++?! То есть, я знаю, что это ошибка, я знаю, что означает ошибка, я просто не понимаю, почему это было бы незаконным!

4b9b3361

Ответ 1

Ваш код (упрощенный):

struct X
{
   int mem;
   void f(int param = mem); //ERROR
};

Вы хотите использовать нестатические данные члена как значение по умолчанию для параметра функции-члена. Первый вопрос, который приходит на ум, таков: какой конкретный экземпляр класса значением по умолчанию mem принадлежит?

X x1 = {100};  //mem = 100
X x2 = {200};  //mem = 200

x1.f(); //param is 100 or 200? or something else?

Ваш ответ может быть 100, поскольку f() вызывается на объекте x1, который имеет mem = 100. Если это так, то для реализации f() требуется реализация:

void f(X* this, int param = this->mem);

который, в свою очередь, требует инициализации первого аргумента перед инициализацией другого аргумента. Но в стандарте С++ не указывается порядок инициализации аргументов функции. Следовательно, это недопустимо. Его по той же причине, что и С++ Standard не позволяет даже этого:

int f(int a, int b = a); //§8.3.6/9

На самом деле в §8.3.6/9 прямо сказано:

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

Остальная часть раздела интересна для чтения.


Интересная тема, связанная с аргументами по умолчанию (не относящаяся к этой теме):

Ответ 2

Аргументы по умолчанию должны быть известны во время компиляции. Когда вы говорите о чем-то вроде вызова функции, тогда функция известна во время компиляции, даже если возвращаемое значение отсутствует, поэтому компилятор может сгенерировать этот код, но когда вы по умолчанию используете переменную-член, t знать, где найти этот экземпляр во время компиляции, а это значит, что ему действительно нужно передать параметр (this), чтобы найти mem. Обратите внимание, что вы не можете сделать что-то вроде void func(int i, int f = g(i));, и оба они фактически имеют одинаковое ограничение.

Я также считаю, что это ограничение глупо. Но тогда С++ полон глупых ограничений.

Ответ 3

Как упоминалось выше DeadMG, что-то вроде

void func(int i, int f = g(i))

является незаконным по той же причине. я полагаю, однако, что это не просто глупое ограничение. Чтобы разрешить такую ​​конструкцию, нам нужно ограничить порядок оценки для параметров функции (поскольку нам нужно рассчитать это до этого → mem), но стандарт С++ явно отклоняет любые предположения относительно порядка оценки.

Ответ 4

Принятый ответ в дублированном вопросе - это почему, но в стандарте также явно указано, почему это так:

8.3.6/9:

" Пример: объявление X:: mem1() в следующем примере плохо сформировано, потому что для нестатического элемента X:: a не используется объект, который используется в качестве инициализатора.

int b;
class X
  int a;
  int mem1(int i = a);    // error: nonstatic member a
                          // used as default argument
  int mem2(int i = b);    // OK: use X::b
  static int b;
};

Объявление X:: mem2() имеет смысл, однако, поскольку для доступа к статическому элементу X:: b не нужен объект. Классы, объекты и элементы описаны в разделе 9. "

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

Ответ 5

ISO С++ раздел 8.3.6/9

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

Также проверьте пример, приведенный в этом разделе.

Ответ 6

По какой-то причине, поскольку f является общедоступным, но mem является закрытым. Таким образом, код выглядит следующим образом:

int main() { 
    X x;
    x.f();
    return 0;
}

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

Кроме того, он (или, по крайней мере, мог бы) также сделать кодирование немного сложнее. Обычно, если компилятор будет использовать аргумент по умолчанию, он получает значение, которое он будет передавать как часть объявления функции. Генерирование кода для передачи этого значения в качестве параметра тривиально. Если вы можете передать член объекта (возможно, вложенный в него как можно глубже), а затем добавить такие вещи, как возможность того, что он является зависимым именем в шаблоне, который может (например) называть другой объект с преобразованием в правильную цель типа, и у вас есть рецепт для создания кода довольно сложно. Я не знаю точно, но я подозреваю, что кто-то подумал о таких вещах, и решил, что лучше оставаться консервативным и, возможно, открывать разламывание позже, если по этой причине было бы разумно. Учитывая количество раз, когда я видел проблемы, возникающие из-за этого, я предполагаю, что это останется так долго, просто потому, что оно редко вызывает проблемы.

Ответ 7

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

Ответ 8

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

Как используется на других языках без аргументов по умолчанию (например, С# pre 4.0)

Просто используйте перегрузку, чтобы обеспечить тот же результат:

struct X
{
   X():mem(42){}
   void f(int param)
   {
      //do something
   }
   void f()
   {
      f(mem);
   }
private: 
   int mem;
};

Ответ 9

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

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

  • Переменные с нестатическим временем жизни не могут использоваться, потому что они могут не существовать во время вызова.
  • Нестатические переменные-члены не могут использоваться, потому что им нужна (неявная) квалификация this->, которая обычно не может быть оценена на сайте вызова.