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

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

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

9 ответов


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

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

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

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

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

ваш ответ 100 as f() вызывается на объект x1 имеющего mem = 100. Если это так, то требуется реализация для реализации f() as:

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

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

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

фактически, §8.3.6 / 9 прямо говорит:

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

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


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


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

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


Как DeadMG упоминал выше, somethig как

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

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


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

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 в этот момент фактически невозможно использовать нестатические переменные-члены в качестве инициализаторов для аргументов по умолчанию.


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

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

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

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


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


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

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

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

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

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

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

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


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

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

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