Можно ли использовать вызов функции-члена в качестве аргумента по умолчанию?

Вот мой код:

struct S
{
   int f() { return 1; }
   int g(int arg = f()) { return arg; }
};

int main()
{
    S s;
    return s.g();
}

Это не скомпилируется с ошибкой:

error: cannot call member function 'int S::f()' without object

Попытка this->f() тоже не работает, поскольку this не может использоваться в этом контексте.

Есть ли способ сделать эту работу, все еще используя аргумент по умолчанию?


Конечно, его можно обойти, не используя аргументы по умолчанию:

int g(int arg) { return arg; }
int g() { return g(f()); }

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

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

Ответы

Ответ 1

Вы можете использовать их только там, если они static. Из проекта С++ 11 (n3299), §8.3.6/9:

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

например. это работает:

struct S {
  static int f() { return 1; }
  int g(int arg = f()) { return arg; }
};

int main()
{
  S s;
  return s.g();
}

Это также работает (я думаю, что означает первое выражение):

struct S {
  int f() { return 42; }
  int g(int arg);
};

static S global;

int S::g(int arg = global.f()) { return arg; }

int main()
{
  S s;
  return s.g();
}

Что касается this, то это действительно не разрешено (§8.3.6/8):

Ключевое слово this не должно использоваться в аргументе по умолчанию для функции-члена.

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

Ответ 2

Если вам разрешено использовать экспериментальные функции из С++ 17, вы можете использовать std::optional из STL (см. здесь для дальнейшего подробнее).

В других терминах что-то вроде:

int g(std::optional<int> oarg = std::optional<int>{}) {
    int arg = oarg ? *oarg : f();
    // go further
}

ИЗМЕНИТЬ

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

int g(std::optional<int> oarg = std::optional<int>{}) {
    int arg = oarg.value_or(f());
    // go further
}

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

Ответ 3

Я добавляю еще один ответ, который полностью отличается от предыдущего и может решить вашу проблему.
Идея заключается в использовании другого класса и правильного сочетания явных и неявных конструкторов.
Это следует за минимальным рабочим примером:

#include <functional>
#include <iostream>

template<class C, int(C::*M)()>
struct Arg {
    std::function<int(C*)> fn;
    Arg(int i): fn{[i](C*){ return i; }} { } 
    explicit Arg(): fn{[](C* c){ return (c->*M)(); }} { }
};

struct S {
    int f() { return 1; }
    int h() { return 2; }
    void g(int arg0,
          Arg<S, &S::f> arg1 = Arg<S, &S::f>{},
          Arg<S, &S::h> arg2 = Arg<S, &S::h>{})
    {
        std::cout << "arguments" << std::endl;
        std::cout << "arg0: " << arg0 << std::endl;
        std::cout << "arg1: " << arg1.fn(this) << std::endl;
        std::cout << "arg2: " << arg2.fn(this) << std::endl;
    }
};

int main() {
    S s{};
    s.g(42, 41, 40);
    s.g(0);
}

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

Из решения, примененного к исходному примеру, следует вопрос:

#include <functional>

template<class C, int(C::*M)()>
struct Arg {
    std::function<int(C*)> fn;
    Arg(int i): fn{[i](C*){ return i; }} { } 
    explicit Arg(): fn{[](C* c){ return (c->*M)(); }} { }
};

struct S {
    int f() { return 1; }
    int g(Arg<S, &S::f> arg = Arg<S, &S::f>{}) {
        return arg.fn(this);
    }
};

int main() {   
    S s{}; 
    return s.g();
}

И все это возможно сделать даже без методов static или глобальных переменных.
Конечно, мы можем как-то использовать это. Это вопрос немного изгибать язык...