3

Is there a nice way to have a non static value as default argument in a function? I've seen some older responses to the same question which always end up in explicitly writing out the overload. Is this still necessary in C++17?

What I'd like to do is do something akin to

class C {
  const int N; //Initialized in constructor

  void foo(int x = this->N){
    //do something
  }
}

instead of having to write

class C {
  const int N; //Initialized in constructor

  void foo(){
    foo(N);
  }

  void foo(int x){
    //do something
  }
}

which makes the purpose of the overload less obvious.

DeinFreund
  • 97
  • 5
  • 1
    Good question, shows research before asked. The reasons for why this is not possible are [here](https://stackoverflow.com/questions/4539406/nonstatic-member-as-a-default-argument-of-a-nonstatic-member-function). However, I don't know about something specific to [tag:C++17].. – gsamaras Jul 20 '19 at 14:19
  • "Not really" is the short answer here. In the past, in similar situations, I ended up either sucking up and declaring an overload (relying on the compiler to optimize out the extra function call), or change the parameters that get passed in, somehow, in order to eliminate the explicit default. – Sam Varshavchik Jul 20 '19 at 14:20
  • @gsamaras Thanks! I suppose this means the problem can't be solved without breaking backwards compatibility. – DeinFreund Jul 20 '19 at 14:23
  • @DeinFreund I don't know, maybe [`std::optional`](https://en.cppreference.com/w/cpp/utility/optional) could come to the rescue, but I would follow Sam's advice and refactor. – gsamaras Jul 20 '19 at 14:26
  • What if N is static? – Michael Chourdakis Jul 20 '19 at 14:44
  • @MichaelChourdakis then it's a whole different question. :) – gsamaras Jul 20 '19 at 14:45
  • If N is static, N can be used. The question should probably also read non-static member, because, the value does not have to be static, it just can't be a non-static member value. – midor Jul 21 '19 at 08:49

1 Answers1

2

One relatively elegant way (in my opinion) would be to use std::optional to accept the argument, and if no argument was provided, use the default from the object:

class C {
  const int N_; // Initialized in constructor
    public:
    C(int x) :N_(x) {}

  void foo(std::optional<int> x = std::nullopt) {
        std::cout << x.value_or(N_) << std::endl;
  }
};

int main() {
  C c(7);
  c.foo();
  c.foo(0);
}

You can find the full explanation of what works/doesn't work in section 11.3.6 of the standard. Subsection 9 describes member access (excerpt):

A non-static member shall not appear in a default argument unless it appears as the id-expressionof a class member access expression (8.5.1.5) or unless it is used to form a pointer to member (8.5.2.1).[Example:The declaration of X::mem1()in the following example is ill-formed because no object is supplied for the non-static memberX::a used as an initializer.

int b;
class X {
   int a;
   int mem1(int i = a);// error: non-static memberaused as default argument
   int mem2(int i = b);// OK; useX::b
   static int b;
};
midor
  • 5,487
  • 2
  • 23
  • 52
  • 1
    Doesn't this replace the compile time differentiation by a run time conditional, which would impact the performance? I also wouldn't want to have to add a list of nulls to a function call. – DeinFreund Jul 20 '19 at 14:27
  • It possibly does, but that can often be optimized out by the compiler (https://godbolt.org/z/7HcJnQ). I am not sure what you mean by a list of nulls tbh, but obviously there are some trade-offs, and if you want this to be discerned at compile time, overloads are likely your best shot. This can also be done with template magic: https://github.com/rmpowell77/LIAW_2017_param, but it requires a non-trivial amount of meta-programming and is currently still not easy to do. – midor Jul 20 '19 at 15:47
  • I would just change `void foo(std::optional x) ` to `void foo(std::optional x = std::nullopt) ` as that would allow to do the call without the optional arguments. – DeinFreund Jul 21 '19 at 14:50