可选的参数后,模板参数包的据称已知长度的参数包

发布于 2025-02-11 22:16:21 字数 832 浏览 1 评论 0原文

我试图用一种可选的参数具有一种“调用”功能:

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);
}

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

clang> 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

为什么要推论长度0的列表?我可以强迫它忽略args以扣除的目的吗?或更一般而言,我该如何完成这项工作?

I am trying to have a kind of "invoke" function with an optional argument at the end:

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);
}

I would have expected this to work, since the parameter pack can be deduced from the first argument, but clearly the compilers have different ideas:

Clang for example gives:

<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

Why is it deducing a list of length 0? Can I force it to ignore args for the purposes of deduction? Or more generally, how can I make this work?

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

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

发布评论

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

评论(2

も星光 2025-02-18 22:16:21

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

然后,第二个过载将使用“默认”参数集将参数转发到第一个。

#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
}

您可能需要将可选的一路移动到第一个位置,以使功能及其参数在一起。这是一个味道。


另一个选项可能是排除可选参数,并且仅具有参数pack,并在存在时提取可选参数,或者如果没有,则使用默认值。
这要求您限制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
}

以第二版为基础,但给予更多自由,您可以为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
}

You could make it overloaded instead of having an optional argument. You'd need to move the "optional" to before the parameter pack though.

The second overload would then just forward the arguments to the first, with the "default" parameter set.

#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
}

You might want to move the optional all the way to the first position to let the function and its parameters be together. It's a matter of taste.


Another option could be to exclude the optional parameter and only have the parameter pack and to extract the optional if it's present or use the default value if it's not.
This requires that you restrict the signature of func to match the function you aim to call.

#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
}

Building on the second version but giving a lot more freedom, you could introduce a separate parameter pack for func. If that pack has the same size as pack of arguments supplied, you need to pick a default value for opt. If it on the other hand contains more arguments than needed for the function, you can select which one of the extra arguments that should be used for opt. In the example below, I just picked the first extra parameter.

#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
}
兔姬 2025-02-18 22:16:21

我该如何使这项工作?

您可以将函数参数放在a std :: tuple < < /a>,使它们与您的可选参数不同。

C ++ 17提供 std :: apply 为您打开元组参数。

#include <tuple>

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

void bar(int, int);

int main()
{
    foo(&bar, {1, 2}, 3);
//       args ^^^^^^
}

how can I make this work?

You can put the function arguments in a std::tuple, to make them distinct from your optional parameter.

C++17 provides std::apply to unpack the tuple parameters for you.

#include <tuple>

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

void bar(int, int);

int main()
{
    foo(&bar, {1, 2}, 3);
//       args ^^^^^^
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文