Передача аргументов в функцию 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++ (кроме способов, перечисленных в предоставленном потоке разработки программного обеспечения).