在 C++ 中通过引用传递时参数的默认值

发布于 2024-07-25 22:23:52 字数 312 浏览 5 评论 0原文

当我们通过引用传递参数时,是否可以为函数的参数指定默认值。 在 C++ 中

例如,

virtual const ULONG Write(ULONG &State = 0, bool sequence = true);

,当我尝试声明如下函数时:当我这样做时,它会给出错误:

错误 C2440:“默认参数”:无法从“const int”转换为“unsigned long &” 不是“const”的引用不能绑定到非左值

Is it possible to give a default value to a parameter of a function while we are passing the parameter by reference. in C++

For example, when I try to declare a function like:

virtual const ULONG Write(ULONG &State = 0, bool sequence = true);

When I do this it gives an error:

error C2440: 'default argument' : cannot convert from 'const int' to 'unsigned long &'
A reference that is not to 'const' cannot be bound to a non-lvalue

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(18

陌上青苔 2024-08-01 22:23:55

使用 MSVC 将我们的代码库从 C++17 升级到 C++20,微软现在终于强制执行了这条规则。

void foo(int& optOutParam = 0) 的语义是 optOutParam 是一个可选的输出参数。
我经常看到这样的具体示例,其中需要多个输出参数:

bool Initialise(Window& wnd, Context& ctxt, std::string& error = "") {
// ...
}

返回值指示函数是否成功,而错误则包含函数可能失败的原因。
不幸的是 std::Optional 被明确禁止引用,否则这也是一个很好的解决方案。

std::optional<int&> opt; // compiler error

重构的一个好方法是让函数实际返回所有值并避免使用引用参数作为输出。

std::tuple<Window, Context, std::string> Initialise();

void modernCpp() {
  auto[wnd, ctxt, errorMsg] = Initialise();
  if(!errorMsg.empty()) {
    exit(1);
  }
}

void olderCpp() {
  Window wnd;
  Context ctxt;
  std::string errorMsg;
  std::tie(wnd, ctxt, errorMsg) = Initialise();

  // or
  tuple<Window, Context, std::string> result = Initialise();
}

不幸的是,这意味着还必须重构每个调用站点,这在大型代码库中可能是一项艰巨的工作。
为了处理这种情况,我使用右值重载,它会丢弃错误消息。
则无需更改调用站点。

bool Initialise(Window& wnd, Context& ctxt, std::string& error) {
  // default value removed
}
bool Initialise(Window& wnd, Context& ctxt, std::string&& discardedErrorMsg = "") {
  // call overload above
  return Initialise(wnd, ctxt, discardedErrorMsg);
}

这有一个主要缺点,即收集错误消息的信息可能会非常昂贵。
例如,询问数据库出了什么问题可能需要另一次网络旅行。
但请记住,原始函数也存在同样的问题,因此这是一个很好的微优化机会。

对于绝大多数具有多个输出参数的函数,我使用多个返回值。

Upgrading our code base from C++17 to C++20 using MSVC, Microsoft now finally enforces this rule.

The semantics for void foo(int& optOutParam = 0) would be optOutParam is an optional output parameter.
Often I see this concrete example, where multiple output parameters are required:

bool Initialise(Window& wnd, Context& ctxt, std::string& error = "") {
// ...
}

The return value indicates whether the function succeeded, while error contains the reason why it might have failed.
Unfortunately std::optional is explicitly forbidden for references, or this would be a great solution, too.

std::optional<int&> opt; // compiler error

A good way to refactor is to actually have the function return all values and refrain from using reference parameters as output.

std::tuple<Window, Context, std::string> Initialise();

void modernCpp() {
  auto[wnd, ctxt, errorMsg] = Initialise();
  if(!errorMsg.empty()) {
    exit(1);
  }
}

void olderCpp() {
  Window wnd;
  Context ctxt;
  std::string errorMsg;
  std::tie(wnd, ctxt, errorMsg) = Initialise();

  // or
  tuple<Window, Context, std::string> result = Initialise();
}

Unfortunately this means having to refactor every call-site as well, which might be a huge job in large code bases.
To deal with that case I use an r-value overload, that discards the error message.
No call-site has to be changed then.

bool Initialise(Window& wnd, Context& ctxt, std::string& error) {
  // default value removed
}
bool Initialise(Window& wnd, Context& ctxt, std::string&& discardedErrorMsg = "") {
  // call overload above
  return Initialise(wnd, ctxt, discardedErrorMsg);
}

This has one major draw-back, namely gathering the info for error message might be very costly.
Asking a database what went wrong, for example, may require another network trip.
Bear in mind though, that the original function has the same issue, so this is a great micro-optimisation chance.

For the vast majority of new functions with multiple out parameters, I use multiple return values.

爱本泡沫多脆弱 2024-08-01 22:23:55
void revealSelection(const ScrollAlignment& = ScrollAlignment::alignCenterIfNeeded, bool revealExtent = false);
void revealSelection(const ScrollAlignment& = ScrollAlignment::alignCenterIfNeeded, bool revealExtent = false);
森罗 2024-08-01 22:23:55

这还有一个相当肮脏的技巧:

virtual const ULONG Write(ULONG &&State = 0, bool sequence = true);

在这种情况下,您必须使用 std::move 调用它:

ULONG val = 0;
Write(std::move(val));

这只是一些有趣的解决方法,我完全不建议在实际代码中使用它!

There is also rather dirty trick for this:

virtual const ULONG Write(ULONG &&State = 0, bool sequence = true);

In this case you have to call it with std::move:

ULONG val = 0;
Write(std::move(val));

It is only some funny workaround, I totally do not recommend it using in real code!

梦年海沫深 2024-08-01 22:23:55

我有一个解决方法,请参阅以下关于 int& 默认值的示例:

class Helper
{
public:
    int x;
    operator int&() { return x; }
};

// How to use it:
void foo(int &x = Helper())
{

}

您可以对任何您想要的简单数据类型执行此操作,例如 bool双 ...

I have a workaround for this, see the following example on default value for int&:

class Helper
{
public:
    int x;
    operator int&() { return x; }
};

// How to use it:
void foo(int &x = Helper())
{

}

You can do it for any trivial data type you want, such as bool, double ...

秋意浓 2024-08-01 22:23:55

定义2个重载函数。

virtual const ULONG Write(ULONG &State, bool sequence = true);

virtual const ULONG Write(bool sequence = true)
{
    int State = 0;
    return Write(State, sequence);
}

Define 2 overload functions.

virtual const ULONG Write(ULONG &State, bool sequence = true);

virtual const ULONG Write(bool sequence = true)
{
    int State = 0;
    return Write(State, sequence);
}
触ぅ动初心 2024-08-01 22:23:55

virtual const ULONG Write(ULONG &State = 0, bool sequence = true);

答案很简单,我不太擅长解释,但如果您想将默认值传递给非常量参数(该参数可能会在此函数中修改),请像这样使用它:

virtual const ULONG Write(ULONG &State = *(ULONG*)0, bool sequence =
> true);

virtual const ULONG Write(ULONG &State = 0, bool sequence = true);

The answer is quite simple and I am not so good on explaining but if you want to pass a default value to a non-const parameter which probably will be modified in this function is to use it like this:

virtual const ULONG Write(ULONG &State = *(ULONG*)0, bool sequence =
> true);
苦行僧 2024-08-01 22:23:54

另一种方式可能如下:

virtual const ULONG Write(ULONG &State, bool sequence = true);

// wrapper
const ULONG Write(bool sequence = true)
{
   ULONG dummy;
   return Write(dummy, sequence);
}

那么可以进行以下调用:

ULONG State;
object->Write(State, false); // sequence is false, "returns" State
object->Write(State); // assumes sequence = true, "returns" State
object->Write(false); // sequence is false, no "return"
object->Write(); // assumes sequence = true, no "return"

Another way could be the following:

virtual const ULONG Write(ULONG &State, bool sequence = true);

// wrapper
const ULONG Write(bool sequence = true)
{
   ULONG dummy;
   return Write(dummy, sequence);
}

then the following calls are possible:

ULONG State;
object->Write(State, false); // sequence is false, "returns" State
object->Write(State); // assumes sequence = true, "returns" State
object->Write(false); // sequence is false, no "return"
object->Write(); // assumes sequence = true, no "return"
最近可好 2024-08-01 22:23:54
void f(const double& v = *(double*) NULL)
{
  if (&v == NULL)
    cout << "default" << endl;
  else
    cout << "other " << v << endl;
}
void f(const double& v = *(double*) NULL)
{
  if (&v == NULL)
    cout << "default" << endl;
  else
    cout << "other " << v << endl;
}
流殇 2024-08-01 22:23:54

在面向对象的情况下...说给定类具有“默认”意味着必须声明此默认值(值),然后可以将其用作默认参数,例如:

class Pagination {
public:
    int currentPage;
    //...
    Pagination() {
        currentPage = 1;
        //...
    }
    // your Default Pagination
    static Pagination& Default() {
        static Pagination pag;
        return pag;
    }
};

在您的方法上...

 shared_ptr<vector<Auditoria> > 
 findByFilter(Auditoria& audit, Pagination& pagination = Pagination::Default() ) {

这个解决方案非常合适因为在这种情况下,“全局默认分页”是单个“参考”值。 您还可以在运行时更改默认值,例如“全球级”配置,例如:用户分页导航首选项等。

In case of OO... To say that a Given Class has and "Default" means that this Default (value) must declared acondingly an then may be usd as an Default Parameter ex:

class Pagination {
public:
    int currentPage;
    //...
    Pagination() {
        currentPage = 1;
        //...
    }
    // your Default Pagination
    static Pagination& Default() {
        static Pagination pag;
        return pag;
    }
};

On your Method ...

 shared_ptr<vector<Auditoria> > 
 findByFilter(Auditoria& audit, Pagination& pagination = Pagination::Default() ) {

This solutions is quite suitable since in this case, "Global default Pagination" is a single "reference" value. You will also have the power to change default values at runtime like an "gobal-level" configuration ex: user pagination navigation preferences and etc..

屋檐 2024-08-01 22:23:54

可以使用 State 的 const 限定符:

virtual const ULONG Write(const ULONG &State = 0, bool sequence = true);

It's possible with const qualifier for State:

virtual const ULONG Write(const ULONG &State = 0, bool sequence = true);
最佳男配角 2024-08-01 22:23:53

不,这是不可能的。

通过引用传递意味着该函数可能会更改参数的值。 如果参数不是由调用者提供并且来自默认常量,那么函数应该更改什么?

No, it's not possible.

Passing by reference implies that the function might change the value of the parameter. If the parameter is not provided by the caller and comes from the default constant, what is the function supposed to change?

花开半夏魅人心 2024-08-01 22:23:53

不能将常量文字用作默认参数,其原因与不能将常量文字用作函数调用的参数的原因相同。 引用值必须有地址,常量引用值则不需要(即它们可以是右值或常量文字)。

int* foo (int& i )
{
   return &i;
}

foo(0); // compiler error.

const int* bar ( const int& i )
{
   return &i;
}

bar(0); // ok.

确保您的默认值有一个地址并且没问题。

int null_object = 0;

int Write(int &state = null_object, bool sequence = true)
{
   if( &state == &null_object )
   {
      // called with default paramter
      return sequence? 1: rand();
   }
   else
   {
      // called with user parameter
      state += sequence? 1: rand();
      return state;
   }
}

我已经使用过这种模式几次,其中我有一个可以是变量或空的参数。 在这种情况下,常规方法是让用户传递一个指针。 如果他们不希望您填写该值,他们会传入 NULL 指针。 我喜欢空对象方法。 它使调用者的生活更轻松,而不会使被调用者代码变得非常复杂。

You cannot use a constant literal for a default parameter for the same reason you cannot use one as a parameter to the function call. Reference values must have an address, constant references values need not (ie they can be r-values or constant literals).

int* foo (int& i )
{
   return &i;
}

foo(0); // compiler error.

const int* bar ( const int& i )
{
   return &i;
}

bar(0); // ok.

Ensure that you're default value has an address and you're fine.

int null_object = 0;

int Write(int &state = null_object, bool sequence = true)
{
   if( &state == &null_object )
   {
      // called with default paramter
      return sequence? 1: rand();
   }
   else
   {
      // called with user parameter
      state += sequence? 1: rand();
      return state;
   }
}

I've used this pattern a few times where I had a parameter that could be a variable or null. The regular approach is to have the user pass in a pointer this is case. They pass in a NULL pointer if they don't want you to fill in the value. I like to null object approach. It makes the callers life easier without terribly complicating the callee code.

南笙 2024-08-01 22:23:53

我认为不是,原因是默认值被评估为常量,并且通过引用传递的值必须能够更改,除非您也将其声明为常量引用。

I think not, and the reason is that default values are evaluated to constants and values passed by reference must be able to change, unless you also declare it to be constant reference.

夜吻♂芭芘 2024-08-01 22:23:52

您可以对常量引用执行此操作,但不能对非常量引用执行此操作。 这是因为 C++ 不允许将临时值(本例中为默认值)绑定到非常量引用。

解决这个问题的一种方法是使用实​​际实例作为默认值:

static int AVAL = 1;

void f( int & x = AVAL ) {
   // stuff
} 

int main() {
     f();       // equivalent to f(AVAL);
}

但这的实际用途非常有限。

You can do it for a const reference, but not for a non-const one. This is because C++ does not allow a temporary (the default value in this case) to be bound to non-const reference.

One way round this would be to use an actual instance as the default:

static int AVAL = 1;

void f( int & x = AVAL ) {
   // stuff
} 

int main() {
     f();       // equivalent to f(AVAL);
}

but this is of very limited practical use.

少女净妖师 2024-08-01 22:23:52

这已经在对您答案的直接评论之一中说过,但只是正式声明。 您想要使用的是重载:

virtual const ULONG Write(ULONG &State, bool sequence);
inline const ULONG Write()
{
  ULONG state;
  bool sequence = true;
  Write (state, sequence);
}

使用函数重载还有其他好处。 首先,您可以默认任何您想要的参数:

class A {}; 
class B {}; 
class C {};

void foo (A const &, B const &, C const &);
void foo (B const &, C const &); // A defaulted
void foo (A const &, C const &); // B defaulted
void foo (C const &); // A & B defaulted etc...

还可以在派生类中为虚函数重新定义默认参数,这可以避免重载:

class Base {
public:
  virtual void f1 (int i = 0);  // default '0'

  virtual void f2 (int);
  inline void f2 () {
    f2(0);                      // equivalent to default of '0'
  }
};

class Derived : public Base{
public:
  virtual void f1 (int i = 10);  // default '10'

  using Base::f2;
  virtual void f2 (int);
};

void bar ()
{
  Derived d;
  Base & b (d);
  d.f1 ();   // '10' used
  b.f1 ();   // '0' used

  d.f2 ();   // f1(int) called with '0' 
  b.f2 ();   // f1(int) called with '0'
}
  

只有一种情况确实需要使用默认值,那就是在构造函数上。 不可能从一个构造函数调用另一个构造函数,因此该技术在这种情况下不起作用。

It has been said in one of the direct comments to your answer already, but just to state it officially. What you want to use is an overload:

virtual const ULONG Write(ULONG &State, bool sequence);
inline const ULONG Write()
{
  ULONG state;
  bool sequence = true;
  Write (state, sequence);
}

Using function overloads also have additional benefits. Firstly you can default any argument you wish:

class A {}; 
class B {}; 
class C {};

void foo (A const &, B const &, C const &);
void foo (B const &, C const &); // A defaulted
void foo (A const &, C const &); // B defaulted
void foo (C const &); // A & B defaulted etc...

It is also possible to redefine default arguments to virtual functions in derived class, which overloading avoids:

class Base {
public:
  virtual void f1 (int i = 0);  // default '0'

  virtual void f2 (int);
  inline void f2 () {
    f2(0);                      // equivalent to default of '0'
  }
};

class Derived : public Base{
public:
  virtual void f1 (int i = 10);  // default '10'

  using Base::f2;
  virtual void f2 (int);
};

void bar ()
{
  Derived d;
  Base & b (d);
  d.f1 ();   // '10' used
  b.f1 ();   // '0' used

  d.f2 ();   // f1(int) called with '0' 
  b.f2 ();   // f1(int) called with '0'
}
  

There is only one situation where a default really needs to be used, and that is on a constructor. It is not possible to call one constructor from another, and so this technique does not work in that case.

世态炎凉 2024-08-01 22:23:52

仍然有提供可选参数的旧 C 方式:当不存在时可以为 NULL 的指针:

void write( int *optional = 0 ) {
    if (optional) *optional = 5;
}

There still is the old C way of providing optional arguments: a pointer that can be NULL when not present:

void write( int *optional = 0 ) {
    if (optional) *optional = 5;
}
忱杏 2024-08-01 22:23:52

这个小模板将帮助您:

template<typename T> class ByRef {
public:
    ByRef() {
    }

    ByRef(const T value) : mValue(value) {
    }

    operator T&() const {
        return((T&)mValue);
    }

private:
    T mValue;
};

然后您将能够:

virtual const ULONG Write(ULONG &State = ByRef<ULONG>(0), bool sequence = true);

This little template will help you:

template<typename T> class ByRef {
public:
    ByRef() {
    }

    ByRef(const T value) : mValue(value) {
    }

    operator T&() const {
        return((T&)mValue);
    }

private:
    T mValue;
};

Then you'll be able to:

virtual const ULONG Write(ULONG &State = ByRef<ULONG>(0), bool sequence = true);
夜吻♂芭芘 2024-08-01 22:23:52

通过引用传递参数有两个原因:(1) 为了性能(在这种情况下您希望通过 const 引用传递)和 (2) 因为您需要能够更改函数内部参数的值。

我非常怀疑在现代架构上传递 unsigned long 会让你的速度减慢太多。 因此,我假设您打算更改方法内 State 的值。 编译器会抱怨,因为常量 0 无法更改,因为它是右值(错误消息中的“非左值”)且不可更改(错误消息中的 const) 。

简单地说,您想要一个可以更改传递的参数的方法,但默认情况下您想要传递一个无法更改的参数。

换句话说,非 const 引用必须引用实际变量。 函数签名中的默认值 (0) 不是真正的变量。 您遇到了同样的问题:

struct Foo {
    virtual ULONG Write(ULONG& State, bool sequence = true);
};

Foo f;
ULONG s = 5;
f.Write(s); // perfectly OK, because s is a real variable
f.Write(0); // compiler error, 0 is not a real variable
            // if the value of 0 were changed in the function,
            // I would have no way to refer to the new value

如果您实际上并不打算更改方法内的 State ,您只需将其更改为 const ULONG& 即可。 但您不会从中获得很大的性能优势,因此我建议将其更改为非引用 ULONG。 我注意到您已经返回了 ULONG,并且我偷偷怀疑它的值是任何需要的修改后的 State 的值。 在这种情况下,我会简单地声明该方法:

// returns value of State
virtual ULONG Write(ULONG State = 0, bool sequence = true);

当然,我不太确定您在写什么或写在哪里。 但这是另一个问题了。

There are two reasons to pass an argument by reference: (1) for performance (in which case you want to pass by const reference) and (2) because you need the ability to change the value of the argument inside the function.

I highly doubt that passing an unsigned long on modern architectures is slowing you down too much. So I'm assuming that you're intending to change the value of State inside the method. The compiler is complaining because the constant 0 cannot be changed, as it's an rvalue ("non-lvalue" in the error message) and unchangeable (const in the error message).

Simply put, you want a method that can change the argument passed, but by default you want to pass an argument that can't change.

To put it another way, non-const references have to refer to actual variables. The default value in the function signature (0) is not a real variable. You're running into the same problem as:

struct Foo {
    virtual ULONG Write(ULONG& State, bool sequence = true);
};

Foo f;
ULONG s = 5;
f.Write(s); // perfectly OK, because s is a real variable
f.Write(0); // compiler error, 0 is not a real variable
            // if the value of 0 were changed in the function,
            // I would have no way to refer to the new value

If you don't actually intend to change State inside the method you can simply change it to a const ULONG&. But you're not going to get a big performance benefit from that, so I would recommend changing it to a non-reference ULONG. I notice that you are already returning a ULONG, and I have a sneaky suspicion that its value is the value of State after any needed modifications. In which case I would simply declare the method as so:

// returns value of State
virtual ULONG Write(ULONG State = 0, bool sequence = true);

Of course, I'm not quite sure what you're writing or to where. But that's another question for another time.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文