代码之家  ›  专栏  ›  技术社区  ›  ModernEraCaveman

是否可以将函数作为参数来重载运算符?

  •  0
  • ModernEraCaveman  · 技术社区  · 1 年前

    我刚刚发现运营商过载,对此我感到非常高兴。在测试其用途时,我遇到了一个我似乎无法工作的案例,而且我找不到合适的资源来解决。

    我试图重载一个将函数作为参数的运算符。我的目标是创建一个可以执行任何给定函数的结构(在合理的范围内,比如遵循相同通用结构的提交命令)。通过 struct class 在这种情况下将不起作用,因为运算符的内部操作将包含execute函数。如果 结构 没有匹配的成员函数,则该操作将不起作用。例如在以下情况下:

    struct s_test1 {
         void funcA() {}
    }
    struct s_test2 {
         void funcB() {}
    }
    struct s_doer {
         friend void operator<<(auto, auto& s_input){
              s_input.funcA(); // would fail for s_test2
              s_input.funcB(); // would fail for s_test1
         }
    }
    

    最终,我想创建这样的伪代码:

    void func0(int& storage) { storage = 0; }
    void func1(int& storage) { storage = 1; }
    void func2(int& storage) { storage = 2; }
    
    void alt_func(int& storage, int alt) { storage = alt; }
    
    struct s_doer {
         friend void operator<<(auto, void(*func)()){
              int storage;
              func(storage);
              cout << storage << endl; // to check if it worked, idk
         }
    }
    
    int main() {
        s_doer{} << func0();
        s_doer{} << func1();
        s_doer{} << alt_func(); // alternatively 's_doer << alt_func(), 0' where '0' is the alt parameter of 'alt_func'
    }
    

    以下是我当前的实现尝试:

    struct s_doer {
        friend void operator<<(auto, void (*func)()) {
            std::cout << "test" << std:endl;
            func();
            std::cout << "test" << std::endl;
        }
    };
    struct s_informer {
        s_informer(int n) {
            info = n;
        }
        void inform() {
            std::cout << info << std::endl;
        }
    private:
        int info;
        friend void operator<<(auto, const void (*func)());
    };
    
    s_informer informer(2);
    
    int main() {
        s_doer{} << informer.inform();
    }
    

    我似乎无法通过考试 no operator "<<" matches these operands 错误

    我的其他一些尝试是:

    friend void operator<<(auto, std::function<void()> func) {...}
    
    template<typename input>
    using Func = void(*)(input in);
    friend void operator<<(auto, Func func) {...}
    
    typedef void (*Func)(int n);
    friend void operator<<(auto, Func func) {...}
    

    是否可以使用功能输入重载运算符?是否也可以在函数重载期间指定参数?

    怎么做?

    2 回复  |  直到 1 年前
        1
  •  2
  •   Ted Lyngmo    1 年前

    我提出了一个替代方案 std::tuple 属于 std::function s,并在完整表达式的末尾调用所有收集的函数。对于需要参数的函数,您可以将这些参数打包到lambda中。

    #include <functional>
    #include <iostream>
    #include <tuple>
    #include <utility>
    
    template <class T = std::tuple<>>
    struct s_doer {
        ~s_doer() {
            // time to die, call the collected functions:
            std::apply([](auto&&... funcs) { ((funcs ? funcs() : void()), ...); }, t);
        }
    
        T t;
    
        template <class F>
        friend auto operator<<(s_doer&& d, F&& f) {
            std::function func{std::forward<F>(f)};
            auto tc = std::tuple_cat(std::move(d.t), std::tuple{std::move(func)});
            // return a new s_doer with the collected functions so far
            return s_doer<decltype(tc)>{std::move(tc)};
        }
    };
    
    int main() {
        s_doer{} << [] { std::cout << "Hello\n"; } << [] { std::cout << "fab\n"; };
    }
    

    输出:

    Hello
    fab
    

    重量更轻的版本 std::函数 s:

    template <class T = std::tuple<>>
    struct s_doer {
        ~s_doer() {
            // time to die, call the collected functions:
            if (last) std::apply([](auto&&... funcs) { (..., funcs()); }, t);
        }
    
        T t;
        bool last = false;
    
        template <class F>
        friend auto operator<<(s_doer&& d, F&& f) {
            auto tc = std::tuple_cat(std::move(d.t), std::tuple{std::forward<F>(f)});
            d.last = false;
            // return a new s_doer with the collected functions so far
            return s_doer<decltype(tc)>{std::move(tc), true};
        }
    };
    
        2
  •  1
  •   HolyBlackCat    1 年前

    informer.inform() 立即调用函数,而您希望将调用延迟到 operator<<

    您可以传递一个调用它的lambda,然后在中调用它 运算符<<

    #include <functional>
    #include <iostream>
    
    struct s_doer
    {
        template <typename F>
        friend void operator<<(s_doer, F &&func)
        {
            std::cout << "test\n";
            std::forward<F>(func)();
            std::cout << "test\n";
        }
    };
    
    struct s_informer
    {
        s_informer(int n)
        {
            info = n;
        }
        void inform()
        {
            std::cout << info << std::endl;
        }
    
      private:
        int info;
    };
    
    s_informer informer(2);
    
    int main()
    {
        s_doer{} << []{informer.inform();};
    }
    

    注意,我已经更改了的第二个参数 运算符<< 要模板化,否则您将无法接受捕获lambda(这有利于通用性,尽管这个特定的lambda没有捕获)。

    我还将第一个参数从 auto s_doer 前者虽然聪明,但可能会产生意想不到的副作用:

    template <typename> struct A {};
    
    int main()
    {
        A<s_doer>{} << []{informer.inform();}; // This actually compiles! But you don't want it to.
    }
    
        3
  •  1
  •   Mooing Duck Human-Compiler    1 年前

    alt_func 有两个参数,不清楚是什么参数 operator<< 应传递给该函数。但如果我们忽略了这一点,那么你的第一次尝试就非常接近了。

    friend void operator<<(auto, void(*func)()) 应该是 friend void operator<<(s_doer&&, void(*func)(int&)) 。您肯定希望尽可能多地指定合理的内容,这就是为什么您要使用 s_doer&& 左边。右边有指向函数的指针,这些函数每个都有一个 int& 参数,并返回void,因此必须指定 int& 参数

    然后打电话时 运算符<< ,你不想通过 后果 的func0,所以您不想调用它,所以不想键入 func0() 。相反,您希望传递函数 它本身 : s_doer{} << func0;

    
    struct s_doer {
         friend void operator<<(s_doer&&, void(*func)(int&)){
              int storage;
              func(storage);
              cout << storage << endl; // to check if it worked, idk
         }
    };
    
    int main() {
        s_doer{} << func0;
        s_doer{} << func1;
        //s_doer{} << alt_func; // alternatively 's_doer << alt_func(), 0' where '0' is the alt parameter of 'alt_func'
    }
    

    http://coliru.stacked-crooked.com/a/e215b7629acf8a76

    让一个参数接受带有 任何 签名,因为无法调用它,因为您不知道要传递哪些参数。因此它毫无用处。但如果需要,您可以使用模板。

    template<class...Args>
    void operator<<(s_doer&&, void(*func)(Args&&...)) {
        func(std::forward<Args>(values)...); 
        //not sure where you plan to get `values` from, but there's ways
    }
    

    还要注意的是,编译器很难优化函数指针,所以我们经常避免它们,并允许函数类型本身作为模板参数,这允许使用functionoid类,比如 std::less 等等。这些可能会导致二进制膨胀,但也更容易优化。