Можно ли использовать вызов функции-члена в качестве аргумента по умолчанию?
Вот мой код:
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
или глобальных переменных.
Конечно, мы можем как-то использовать это. Это вопрос немного изгибать язык...