Передача аргументов в функцию C++ без определенного порядка

Допустим, у меня есть функция foo, которая принимает два непримитивных объекта разных типов, например:

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

если предположить, что размещение этих аргументов не влияет на вывод функции, то теоретически foo(h,d) = foo (d,h). но C++ не позволяет этого, если я не перегружу эту функцию:

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

но эта перегрузка становится проблемой, если число аргументов увеличивается (например, перегрузка 6 для 3 аргументов и т. д.).

Мой вопрос заключается в том, существует ли какой-либо простой способ добиться удобства передачи аргументов не в определенном порядке без повторной перегрузки?

6 ответов

Вполне возможно переупорядочить n аргументов с помощью O(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

Не воспринимайте это как одобрение этой техники. Скорее: даже если это возможно, пожалуйста, не делайте этого.

В C++17 мы имеем std::get<T>(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 ) ... ) ); }

(Это нормально, чтобы умножить делать move(arg_set) до тех пор, пока к взаимно исключающей его части обращаются каждый раз.)

Вообще говоря, лучше уменьшить количество параметров, передаваемых в функцию. Есть два способа сделать то, что вы хотите.

  • Создайте класс, который имеет эти не примитивные данные в качестве переменных-членов. После инициализации этих членов вы можете вызвать функцию-член, которая работает с этими данными.

  • Инкапсулируйте эти не примитивные данные в объект и передайте этот объект по ссылке к вашей функции.

В любом случае вам не нужно беспокоиться о порядке аргументов, если все аргументы инициализированы.

Есть ли простой способ добиться удобства передачи аргументов не в определенном порядке без повторной перегрузки?

Да, вы можете сделать это, но поскольку вы еще не сказали, как именно вы используете эту функцию, я не могу дать вам универсальное решение этой проблемы. В любом случае, для всех людей, заявляющих в комментариях, что поведение, которое необходимо автору вопроса, является неправильным, плохим, глупым и т. Д., Пожалуйста, рассмотрите следующий фрагмент кода, который работает, и я вижу в этом потенциал.

Давайте предположим, что у нас есть класс, в котором есть поля, в которых хранятся некоторые значения, и мы хотим иметь установщики (и, вероятно, также получатели) для доступа к этим значениям (для ясности я представил только минимальную версию).

Итак, этот класс будет:

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

Этот класс не имеет сеттеров или геттеров, потому что мы собираемся обернуть его в другой класс, 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 Функция-член, которая принимает произвольное число или аргументы, и каждый из них может быть (и в этом примере должен быть) другого типа. Установить A поля, как вы видите в (X) вам нужно передать объект типа, который имеет указатель на 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

Как я уже сказал, я не знаю, соответствует ли это вашим потребностям, но это всего лишь пример того, как это сделать, и это может быть полезно для вас.

Тем не менее, размещение значений в не указанном порядке (в сочетании с произвольным числом значений) имеет преимущество в том, что вы можете устанавливать только те значения, которые необходимы.

Вы можете сделать это, используя иерархию, чтобы сделать это в качестве примера

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)
{

}

При необходимости вы можете изменить объект с помощью m_type в myFunction или реализовать глобальную функцию в родительском классе.

Другим способом может быть использование шаблонов.

Насколько я могу судить, похоже, что не существует способа репликации аргументов ключевых слов в стиле Python в C++ (кроме способов, перечисленных в предоставленном потоке разработки программного обеспечения).

Другие вопросы по тегам