【问题标题】:passing arguments to a c++ function without specific order将参数传递给没有特定顺序的 c++ 函数
【发布时间】:2017-02-10 05:01:56
【问题描述】:

假设我有一个函数 foo,它需要两个不同类型的非原始对象,例如:

void foo(House h, Dog d) {// implementation  }

假设这些参数的放置对函数输出没有影响,那么理论上 foo(h,d) = foo (d,h)。但是 c++ 不允许这样做,除非我通过以下方式重载此函数:

void foo (Dog d, House h) {// implementation   }

但是如果参数的数量增加,这种重载就会让人头疼(例如,6 个重载用于 3 个参数等等)。

我的问题是,有没有什么简单的方法可以在不重复重载的情况下实现不按特定顺序传递参数的便利?

【问题讨论】:

  • 只是出于好奇,为什么有人想以错误的顺序将参数传递给函数?
  • 为什么需要这种灵活性?如果你构造它,你可以按照定义的顺序传递它们。
  • 如果您知道该函数需要 3 种不同类型的参数,那么以任何顺序传递它们而不是检查函数原型会不会很方便?
  • 将所有这些参数封装在一个对象中,并将对象引用传递给函数。
  • 这不是重点!从数学上讲,如果 f(x,y) 的输出与 f(y,x) 相同,我为什么要关心参数的正确顺序?

标签: c++ function overloading


【解决方案1】:

一般来说,最好减少传递给函数的参数数量。有两种方法可以做你想做的事。

  • 创建一个将这些非原始数据作为成员变量的类。一旦这些成员被初始化,你就可以调用一个成员函数来操作这些成员数据

  • 将这些非原始数据封装在一个对象中,并通过引用您的函数来传递此对象。

无论哪种方式,只要所有参数都已初始化,您就无需担心参数的顺序

【讨论】:

  • 我同意封装可能是一种解决方案,但是为每个独立函数创建一个类或结构是不切实际的。是吗?也许我错了,或者我问了一个不好的问题。
  • 不,我不提倡为每个独立函数创建一个类或结构。基本上,您需要确定哪些函数对哪些数据进行操作,并且是否可以将这些数据以封装格式组合在一起,以便您有相关的成员函数对他们感兴趣的数据进行操作。另外,通常需要注意的另一件事是一个类或一个函数应该有单一的职责。
  • 你将参数传递给你想让他做的这个类的构造函数的顺序是什么?
  • @user12341234,在这种情况下,可以使用构造函数来默认构造成员,并且OP可能必须依赖Setter函数来完成初始化。
【解决方案2】:

很有可能使用 O(n) 包装器重新排序 n 个参数:

#include <iostream>
using namespace std;
struct A { int a; A(int a) : a(a) {} };
struct B { int b; B(int b) : b(b) {} };
struct C { int c; C(int c) : c(c) {} };
struct D { int d; D(int d) : d(d) {} };
static void foo(A a, B b, C c, D d) { cout << a.a << " " << b.b << " " << c.c << " " << d.d << endl; }
template<class ...Args> struct Foo { void operator()(Args...); };
template<class ...Args> static void foo(Args ...args) { Foo<Args...>()(args...); }
template<class T, class U> struct Foo<T, U, C, D> { void operator()(T t, U u, C c, D d) { foo(u, t, c, d); } };
template<class T, class U, class V> struct Foo<T, U, V, D> { void operator()(T t, U u, V v, D d) { foo(v, t, u, d); } };
template<class T, class U, class V, class W> struct Foo<T, U, V, W> { void operator()(T t, U u, V v, W w) { foo(w, t, u, v); } };
int main() {
    foo(A(1), B(2), C(3), D(4));
    foo(D(5), C(6), B(7), A(8));
    return 0;
}

(包装类 Foo 是必需的,因为函数不能部分特化。)

$ c++ -std=c++11 a.cc
$ ./a.out
1 2 3 4
8 7 6 5

请勿将此解释为对该技术的认可。而是:即使可能,也请不要这样做。

【讨论】:

    【解决方案3】:

    在 C++17 中,我们有 std::get&lt;T&gt;(std::tuple)。这可以与std::forward_as_tuple结合使用:

    template< typename ... unordered_arg >
    void foo( std::tuple< unordered_arg ... > arg_set ) {
        House h = std::get< House && >( std::move( arg_set ) );
        Dog d = std::get< Dog && >( std::move( arg_set ) );
    
        // implementation
    }
    
    template< typename ... arg >
    void foo( arg ... a )
        { foo_impl( std::forward_as_tuple( std::move( a ) ... ) ); }
    

    (可以乘以do move(arg_set),只要每次访问它的互斥部分即可。)

    【讨论】:

    • 元组包含引用,您尝试将get 裸类型输出。您必须编写自己的 get 来衰减元组中的类型,然后再尝试将它们与其参数匹配以使这种方法起作用。
    • @yurikilochek 谢谢,已修复。 (我没有重新实现/填充get,而是切换到按值传递。)
    • 我真的很喜欢你的回答。我能看到的唯一问题是,如果你有两次相同的类型,它就是 UB,因为 std::get 的工作原理。我错了吗?这可能是一个强约束。
    • @skypjack 根据cppreference,这是一个编译时错误。这里的排列没有重复规则,所以默认情况下失败似乎是一种好的行为。
    【解决方案4】:

    As far as I can tell,似乎没有办法在 C++ 中复制 Python 风格的关键字参数(除了提供的软件工程线程中列出的方法。

    【讨论】:

      【解决方案5】:

      您可以使用层次结构来做到这一点,就像这个例子一样

      class Object
      {
      public:
          enum Type { Dog, House};
          Type m_type;
      };
      
      class Dog : public Object
      {
      
      };
      
      class House : public Object
      {
      
      };
      
      void myFunction(const Object& a, const Object& b)
      {
      
      }
      

      如果需要,您可以使用 myFunction 中的 m_type 重铸对象或在父类上实现全局函数。

      另一种方法可能是使用模板。

      【讨论】:

      • 这抛弃了任何编译时安全的概念。您需要 dynamic_casts 来检索类型。
      【解决方案6】:

      是否有任何简单的方法可以方便地传递参数而不是按特定顺序而不重复重载?

      是的,您可以这样做,但是由于您没有说明您对此功能的确切用途是什么,因此我无法为您提供通用解决方案。无论如何,对于所有在 cmets 中声称问题作者所需要的行为是错误的、坏的、愚蠢的等的人,请考虑以下一段代码,它有效,我看到了这方面的潜力。

      假设我们有一个类,它的字段存储一些值,我们希望有一个 setter(可能还有 getter)来访问这些值(为了清楚起见,我只提供了最低版本)。

      所以,这个类将是:

      struct A {
          int g1 {};
          int g2 {};
          int g3 {};
      };
      

      这个类没有 setter 或 getter,因为我们要将它包装在不同的类中,一个 Data 类。

      struct Data {
          template <typename... Ts>
          void set (Ts&&... ts) {
              set_impl (std::forward<Ts> (ts)...);
          }
      
          A a;
      private:
          template <typename T>
          void set_impl (T&& t) {
              a.*T::mem = t.v; // (X)
          }
      
          template <typename T, typename K, typename... Ts>
          void set_impl (T&& t, K&& k, Ts&&... ts) {
              set_impl (std::forward<T> (t));
              set_impl (std::forward<K> (k), std::forward<Ts> (ts)...);
          }
      };
      

      所以这里我们有一个类,它有set 成员函数,它接受任意数量或参数,并且它们中的每一个都可以(并且在这个例子中应该是)不同的类型。要像在(X) 中看到的那样设置A 字段,您需要传递一个类型的对象,该对象具有指向A 成员的指针。所以请看看以下课程。

      struct G {
          G (int c = {}) : v {c} {}
      
          int v;
      };
      
      struct G1 : G { using G::G; static constexpr int A::* mem = &A::g1; };
      struct G2 : G { using G::G; static constexpr int A::* mem = &A::g2; };
      struct G3 : G { using G::G; static constexpr int A::* mem = &A::g3; };
      

      所有Gx 函数在这里仅用于设置A 字段的值,并且每个函数都知道要设置哪个字段。

      显示结果的辅助函数是:

      void show (const A& v) {
          std::cout << "g1 = " << v.g1 << std::endl;
          std::cout << "g2 = " << v.g2 << std::endl;
          std::cout << "g3 = " << v.g3 << std::endl;
      }
      

      最后是用法:

      Data d;
      d.set (G1 {10}, G2 {20}, G3 {30});
      show (d.a);
      
      Data p;
      p.set (G3 {40}, G1 {-30}, G2 {120});
      show (p.a);
      

      这给了我们输出:

      g1 = 10
      g2 = 20
      g3 = 30
      g1 = -30
      g2 = 120
      g3 = 40
      

      就像我说的,我不知道这是否符合您的需求,但这只是一个示例,它可能对您有所帮助。

      尽管如此,将值以未指定的顺序(与任意数量的值结合)具有优势,您可以只设置那些需要的值。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-03-04
        • 2021-09-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-02-13
        相关资源
        最近更新 更多