V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Contextualist
V2EX  ›  C++

parameter pack 仅作为部分参数的问题

  •  
  •   Contextualist ·
    Contextualist · 2022-07-02 07:32:39 +08:00 · 1646 次点击
    这是一个创建于 879 天前的主题,其中的信息可能已经有所发展或是发生改变。
    #include <iostream>
    #include <functional>
    
    template <typename ... Args>
    int invoke(const std::function<int(Args ..., bool)>& f, Args&& ... args) {
    	int r0 = f(std::forward<Args>(args)..., false);
    	int r1 = f(std::forward<Args>(args)..., true);
    	return r0 + r1;
    }
    
    int g(int a, int b, bool c) {
    	if (c) {
    		return a + b;
    	}
    	return a - b;
    }
    
    int main() {
    	int r = invoke(std::function<int(int,int,bool)>(g), 2, 3);
    	std::cout << r << std::endl;
    }
    

    上面的例子在 GCC 里类型推导不出来。如果 invokefconst std::function<int(bool, Args ...)>& 则可以推导出来,但像这种附加的参数在 Args 后面的情况怎么办呢?

    6 条回复    2022-07-02 09:20:03 +08:00
    GeruzoniAnsasu
        1
    GeruzoniAnsasu  
       2022-07-02 07:58:46 +08:00
    应该无解,不如说这看着像个缺陷,不应该合法才对

    无论是可变参数 va_args 也好,还是可变模板参数 typename ... TS 也好,本来都只能作为最后一个参数。这个 case 推导不出来不奇怪,但居然没报错
    geelaw
        2
    geelaw  
       2022-07-02 08:03:08 +08:00   ❤️ 1
    你可以用 SFINAE 的思路

    #include<utility>
    #include<functional>

    template <typename TCallable, typename ...TArgs>
    auto invoke_functor(TCallable &&f, TArgs&& ...args) -> std::enable_if_t<std::is_same_v<decltype(f(std::forward<TArgs>(args)..., std::declval<bool>())), int>, int>
    {
    int r0 = f(std::forward<TArgs>(args)..., false);
    int r1 = f(std::forward<TArgs>(args)..., true);
    return r0 + r1;
    }

    template <typename TCallable, typename ...TArgs>
    auto invoke_function(const TCallable &f, TArgs&& ...args) -> std::enable_if_t<std::is_same_v<TCallable, std::function<int(TArgs..., bool)>>, int>
    {
    int r0 = f(std::forward<TArgs>(args)..., false);
    int r1 = f(std::forward<TArgs>(args)..., true);
    return r0 + r1;
    }

    int g(int a, int b, bool c)
    {
    return c ? a + b : a - b;
    }

    #include<iostream>

    int main()
    {
    std::cout << invoke_functor(g, 2, 3) << std::endl;
    std::cout << invoke_function(std::function<int(int, int, bool)>(g), 2, 3) << std::endl;
    // Does not work.
    // std::cout << invoke_function(g, 2, 3) << std::endl;
    }
    Contextualist
        3
    Contextualist  
    OP
       2022-07-02 08:53:50 +08:00
    @GeruzoniAnsasu (我同事说 clang 可过编译🤦‍♂️)对,语法上确实说不过去,但是语义上来说,确实有这个需求。一般来说,可变参数一般用于“其余的可选参数”,但在这里我想表达更类似宏:对于一组参数,搭配不同的可选参数遍历执行。

    @geelaw 确实可行,type traits 魔法,学习了
    dangyuluo
        4
    dangyuluo  
       2022-07-02 09:07:23 +08:00
    @geelaw `invoke_function`里 SFINAE 的方法是比较两个`std::function`是否相同,请问比`invoke_functor`里检查返回值好在哪里呢。

    另添加了个`std::is_invokable`的例子 https://godbolt.org/z/1jf5Ej9v4
    geelaw
        5
    geelaw  
       2022-07-02 09:14:44 +08:00
    @dangyuluo 因为可调用的东西不一定是 std::function ,还可以是 lambda 表达式、函数之类的。从 lambda 表达式、函数建立 std::function 有额外的内存分配,对性能不好。此外你也看到了 invoke_function 不能接受函数的名字,必须手工构造一个 std::function 。

    更好的思路是检查这个东西是否可以赋值给 int ,用

    std::is_same_v<
    std::declval<int &>() = f(std::forward<TArgs>(args)..., std::declval<bool>()),
    int &
    >

    这样也可以接受返回 const int &、int &、short 等的可调用的东西。
    geelaw
        6
    geelaw  
       2022-07-02 09:20:03 +08:00
    @dangyuluo #4 我在 #5 的回复里误解了你的意思,实现 invoke_function 只是为了复现楼主一开始想要实现的版本。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5844 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 03:31 · PVG 11:31 · LAX 19:31 · JFK 22:31
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.