假定已知长度的模板参数包之后的可选参数

Luk*_*ang 6 c++ templates optional-parameters variadic-templates

我试图拥有一种最后带有可选参数的“调用”函数:

template <typename... T>
void foo(void func(T...), T... args, int opt = 0)
{
    func(args...);
}

void bar(int, int);

int main()
{
    foo(&bar, 1, 2, 3);
}
Run Code Online (Sandbox Code Playgroud)

我本来希望这是可行的,因为参数包可以从第一个参数推导出来,但显然编译器有不同的想法:

例如,Clang 给出:

<source>:11:5: error: no matching function for call to 'foo'
    foo(&bar, 1, 2, 3);
    ^~~
<source>:2:6: note: candidate template ignored: deduced packs of different lengths for parameter 'T' (<int, int> vs. <>)
void foo(void func(T...), T... args, int opt = 0)
     ^
1 errors generated.
Compiler returned: 1
Run Code Online (Sandbox Code Playgroud)

为什么它会推导出长度为0的列表?args我可以为了扣除的目的而强制忽略它吗?或者更一般地说,我怎样才能做到这一点?

Ted*_*gmo 3

您可以使其重载,而不是使用可选参数。不过,您需要将“可选”移至参数包之前。

然后,第二个重载会将参数转发给第一个重载,并设置“默认”参数。

#include <iostream>

template <typename... T>
void foo(void(func)(T...), int opt, T... args)
{
    std::cout << opt << '\n';
    func(args...);
}

template <typename... T>
void foo(void(func)(T...), T... args)
{
    return foo(func, 0, args...);    // forwards with the default set
}

void bar(int, int) {}

int main()
{
    foo(&bar, 1, 2);      // prints 0
    foo(&bar, 3, 1, 2);   // prints 3
}
Run Code Online (Sandbox Code Playgroud)

您可能希望将可选值一直移动到第一个位置,以使函数及其参数放在一起。这是一个品味问题。


另一种选择可能是排除可选参数,仅包含参数包,并提取可选参数(如果存在)或使用默认值(如果不存在)。这要求您限制 的签名func以匹配您要调用的函数。

#include <iostream>
#include <tuple>

template <class... T>
void foo(void func(int, int), T&&... args) {
    int opt = [](T... args) {
        if constexpr (sizeof...(T) > 2) return std::get<2>(std::tuple{args...});
        else return 0;  // use the default
    }(args...);

    std::cout << opt << '\n';

    [&func](int a, int b, auto&&...) { func(a, b); }(args...);
}

void bar(int, int) {}

int main() {
    foo(&bar, 1, 2);     // prints 0
    foo(&bar, 1, 2, 3);  // prints 3
}
Run Code Online (Sandbox Code Playgroud)

在第二个版本的基础上构建,但提供了更多的自由,您可以为func. 如果该包的大小与提供的参数包的大小相同,则需要选择 的默认值opt。另一方面,如果它包含的参数多于函数所需的参数,您可以选择应使用哪个额外参数opt。在下面的示例中,我刚刚选择了第一个额外参数。

#include <iostream>
#include <tuple>
#include <type_traits>
#include <utility>

// a helper to split a tuple in two:
template <class... T, size_t... L, size_t... R>
auto split_tuple(std::tuple<T...> t,
                     std::index_sequence<L...>,
                     std::index_sequence<R...>)
{
    return std::pair{
               std::forward_as_tuple(std::get<L>(t)...),
               std::forward_as_tuple(std::get<R+sizeof...(L)>(t)...)
           };
}

template <class... A, class... T>
void foo(void func(A...), T&&... args) {
    static_assert(sizeof...(T) >= sizeof...(A));
    
    // separate the needed function arguments from the rest:
    auto[func_args, rest] = 
        split_tuple(std::forward_as_tuple(std::forward<T>(args)...),
                    std::make_index_sequence<sizeof...(A)>{},
                    std::make_index_sequence<sizeof...(T)-sizeof...(A)>{});

    int opt = [](auto&& rest) {
        // if `rest` contains anything, pick the first one for `opt`
        if constexpr(sizeof...(T) > sizeof...(A)) return std::get<0>(rest);
        else return 0; // otherwise return a default value
    }(rest);

    std::cout << opt << '\n';

    std::apply(func, func_args);
}

void bar(int a, int b) {
    std::cout << a << ',' << b << '\n';
}

int main() {
    foo(&bar, 1, 2);        // prints 0 then 1,2
    foo(&bar, 1, 2, 3, 4);  // prints 3 then 1,2
}
Run Code Online (Sandbox Code Playgroud)