成员函数调用可作为默认参数吗?

20

这是我的代码:

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. 这个问题乍一看似乎相似,但实际上他询问的是如何形成闭包,这是另一个问题(同时链接提供的解决方案并不适用于我的情况)。


应该是 int g() { return g(f()); },对吧?至少在实际代码中,这样做可能更有意义。当然,在这里它只适用于你的 g 函数中只有一个 return 语句。 - skypjack
@skypjack 谢谢,已修复。 - M.M
3个回答

18

如果它们是static的,你才能在那里使用成员变量。来自于C++11标准草案(n3299),§8.3.6/9:

同样的,非静态成员变量不得在默认参数中使用,即使它没有被评估,除非它以类成员访问表达式(5.2.5)的id-expression形式出现,或者除非它用于形成成员指针(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上的默认参数页面有很多关于这个主题的细节 - 它可能会变得相当复杂。


6
如果您被允许使用C++17的实验性功能,则可以使用STL中的std::optional(有关详细信息,请参见此处)。
换句话说,类似于:
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
如果该函数很耗费资源,也许不值得这样做。

糟糕,在我的真实代码中,参数是通过const引用而不是按值传递的。我一开始没有意识到这会有所不同。不过我认为你的解决方案仍然适用(可以将包装的类型更改为reference_wrapper或其他)。 - M.M
@M.M 是的,它仍然适用。无论如何,我已经添加了另一个答案,提供了可能更好的解决方案(至少是一个起点)。如果这是一个适合您的方法,请告诉我。 - skypjack
只是一个小建议 - 我认为在这种情况下应该使用value_or而不是?: - Predelnik
@Predelnik 好的,我正在更新答案。谢谢。 - skypjack

5

我另外提供一个答案,与之前的完全不同,可以解决您的问题。
这个想法是使用另一个类,并正确地混合显式和非显式构造函数。
下面是一个最小化、可工作的示例:

#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 方法或全局变量实现。
当然,我们可以以某种方式使用 this。这只是需要对语言进行一些弯曲处理...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接