在C++中是否有类似于C#中的params关键字?

23

这就是问题所在。

背景:C# Params

在C#中,您可以将方法/函数的最后一个参数声明为“params”,它必须是单维数组,例如:

public void SomeMethod(int fixedParam, params string[] variableParams)
{
   if (variableParams != null)
   {
        foreach(var item in variableParams)
        {
            Console.WriteLine(item);
        }
   }
}

这基本上允许语法糖在调用站点隐式地构建零个或多个元素的数组:

SomeMethod(1234); // << Zero variableParams
SomeMethod(1234, "Foo", "Bar", "Baz"); // << 3 variableParams

但是仍然可以绕过语法糖并显式地传递一个数组:

SomeMethod(1234, new []{"Foo", "Bar", "Baz"});

3
请问您能告诉我们C++程序员,“C# params”是什么意思吗? - Johannes Schaub - litb
1
@JohannesSchaub-litb 这是关键字 params - jxramos
5个回答

29

是的,在标准C++中,您可以使用va_arg和...语法。请参阅MSDN了解详情

对于C++/CLI,有一个快捷方式。

您可以这样做:

void TheMethod( String^ firstArgument, ... array<Object^>^ variableArgs );

点击这个博客链接查看详情。


很高兴知道这个……我不知道C++有这个托管代码的扩展。 - LBushkin
@LBushkin:更新为更好的语法。 - Reed Copsey

17

对于没有经过管理的C++代码,没有相同便捷的语法。

但是C++中有支持函数的可变参数列表

基本上,您声明一个带有省略号(...)的最后一个参数的函数,并在函数体内使用va_start()/va_arg()调用来解析提供的参数列表。

这种机制不是类型安全的,调用者可以传递任何类型的值,因此您应该清楚地记录函数的公共接口以及您期望传递的内容。

对于托管的C++代码,请参阅Reed的评论。


9
现在,随着现代C++的发展,您可以针对可变参数函数使用现代类型安全实践。
如果所有参数都具有相同的类型,请使用可变模板或std::initializer_list。
使用可变模板,您可以使用递归来遍历可变参数列表。以下是可变模板示例:
template<class T>
void MyFoo(T arg)
{
    DoSomething(arg);
}
template<class T, class... R>
void MyFoo(T arg, R... rest)
{
    DoSomething(arg);
    // If "rest" only has one argument, it will call the above function
    // Otherwise, it will call this function again, with the first argument
    // from "rest" becoming "arg"
    MyFoo(rest...); 
}

int main()
{
    MyFoo(2, 5.f, 'a');
}

这保证了如果DoSomething或者在递归调用MyFoo之前运行的其他代码对于传递给函数MyFoo的每个参数类型都有一个重载,那么就会调用该精确的重载。
使用std::initializer_list,您可以使用简单的foreach循环遍历参数。
template<class T>
void MyFoo(std::initializer_list<T> args)
{
    for(auto&& arg : args)
    {
        DoSomething(arg);
    }
}
int main()
{
    MyFoo({2, 4, 5, 8, 1, 0}); // All the arguments have to have the same type
}

值得注意的是,您还可以结合使用“static_assert”和可变模板,以创建自己的灵活的初始化列表。例如,您可以将std::is_base_of与对类型T的“static_assert”相结合,以确保所有类型都是某个基类的派生类。 - atlaste

0
是的!C++11及以上版本允许函数模板接受类型安全的可变数量参数,创建所谓的“参数包”。你可以将其解包成一个std :: array等等,得到类似于你要找的东西:
struct S {
    template <typename... Args>
    void SomeMethod(int someParam, Args&&... args) const {
        //^ The "&&" here is a "forwarding reference" because Args is a template.
        // Next line isn't required but arguably helps with error messages:
        static_assert((std::is_convertible_v<Args, std::string_view> && ...), "All args in the pack must convert to string_view.");
        // Convert args... to a std::array<std::string_view, N>:
        const auto variableParams = std::array<std::string_view, sizeof...(args)>{std::forward<Args>(args)...};
        if (not variableParams.empty()) { //< Not needed because it's a nop to iterate over an empty array:
            for (const auto& item : variableParams) {
                fmt::print("{}\n", item);
            }
        }
    }
};

int main() {
    S s;
    s.SomeMethod(42, "foo", "bar", "baz");
}

输出:

foo
bar
baz

https://godbolt.org/z/PbPb7qTv9

在C++20中,您可以使用概念来获得更好的错误消息并更加简洁:
    void SomeMethod(int someParam, 
                    std::convertible_to<std::string_view> auto&&... args) const {

正常情况:https://godbolt.org/z/aTG74Wx7j 错误情况:https://godbolt.org/z/jToxYMThs


-1

在boost库中有一个命名参数库(如果我正确理解了C#中的params)。它允许编写如下函数:

int y = lib::f(_name = "bob", _index = 2);

无法确定是否存在重大开销。


3
C# 的 params 不是命名参数,而是可变长度参数列表。 - Reed Copsey

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