Каков канонический способ заставить функцию принять любую перестановку своего списка аргументов? - PullRequest
0 голосов
/ 16 августа 2011

Предположим, у меня есть

class A,B,C;

const A a_def;
const B b_def;
const C c_def;

void f(A a=a_def, B b=b_def, C c=c_def);

Это позволяет, если я хочу использовать параметры по умолчанию, разрешить мне пропустить только c, или b и c, или все трииз них - но не только a или b в одиночку.Однако, поскольку типы аргументов не могут быть смешаны, было бы совершенно недвусмысленно вызывать f(A(), C()) (или фактически f(B(), C(), A()): порядок аргументов произвольный и фактически бессмысленный).

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

void f(A a, C c,       B b=b_def) { f(a,b,c); }
void f(B b, A a=a_def, C c=c_def) { f(a,b,c); }
void f(B b, C c,       A a=a_def) { f(a,b,c); }
void f(C c, A a=a_def, B b=b_def) { f(a,b,c); }
void f(C c, B b,       A a=a_def) { f(a,b,c); }

, что приемлемо только для трех параметров (3! = 6 перестановок), но становится утомительным при четырех (4! = 24 перестановок)и вне пределов при пяти параметрах (5! = 120 перестановок).

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

Ответы [ 4 ]

2 голосов
/ 16 августа 2011

Создать структуру для передачи параметров.

class Params{
 public:
   Params();// set defaults
   Params& A(int);
   Params& B(int);
   int a,b;
};

, затем позвоните

 f(Params().A(5));
1 голос
/ 16 августа 2011

ИМХО, как правило, лучшим решением является определение структуры для передачи параметров.

Но вы заявляете, что не хотите этого, вам нужна нормальная запись.

В этом случае библиотека Boost Parameters является самой простой.

Если вы не можете использовать Boost, вы можете сделать это самостоятельно.

Тем не менее, количество кода для DIY-решения типа Boost Parameters, квадратичного по количеству аргументов, хотя квадратичное намного лучше, чем факториального, оно все еще своего рода запретительно & hellip; Я приведу пример кода ниже. Основная идея состоит в том, чтобы идентифицировать аргументы по типам, а затем они легко сортируются во время компиляции.

template< class Type >
struct Pointer
{
    Type const* p_;
    Pointer( Type* p = 0 ): p_( p ) {}
};

template< class Type, class Args >
Type const* pointer( Args const& args )
{
    return static_cast< Pointer< Type > const& >( args ).p_;
}

template< class Type, class Args >
Type const*& pointerRef( Args& args )
{
    return static_cast< Pointer< Type >& >( args ).p_;
}


//-----------------------------------------------------

class A {};  class B {};  class C {};
A const a_def;  B const b_def;  C const c_def;

void foo( A const* pa, B const* pb, C const* pc )
{
    A const&    a = (pa? *pa : a_def);
    B const&    b = (pb? *pb : b_def);
    C const&    c = (pc? *pc : c_def);

    // Whatever, use a b c here.
}

struct FooArgs
    : Pointer< A >
    , Pointer< B >
    , Pointer< C >
{};

void foo( FooArgs const& args )
{
    foo( pointer< A >( args ), pointer< B >( args ), pointer< C >( args ) );
}

void foo()
{
    foo( FooArgs() );
}

template< class T1 >
void foo( T1 const& v1 )
{
    FooArgs     args;

    pointerRef< T1 >( args ) = &v1;
    foo( args );
}

template< class T1, class T2 >
void foo( T1 const& v1, T2 const& v2 )
{
    FooArgs     args;
    pointerRef< T1 >( args ) = &v1;
    pointerRef< T2 >( args ) = &v2;
    foo( args );
}

template< class T1, class T2, class T3 >
void foo( T1 const& v1, T2 const& v2, T3 const& v3 )
{
    FooArgs     args;
    pointerRef< T1 >( args ) = &v1;
    pointerRef< T2 >( args ) = &v2;
    pointerRef< T3 >( args ) = &v3;
    foo( args );
}

int main()
{
    foo( B() );
}

Как уже упоминалось, эта вещь DIY была бы моим последним выбором.

Я пропустил проверку ошибок в этом примере кода. Например, код не должен компилироваться, если два или более фактических аргумента имеют одинаковый тип. Я также опустил обобщение на случай, когда формальные аргументы не все одного типа. Эти пропущенные вещи способствуют большей сложности. Так что, если вышеперечисленное кажется непомерным, подумайте, как оно выглядит в «полной версии», так сказать.

Для случая, когда вам нужны строго типизированные необязательные аргументы, например, для конструкторы в иерархии классов, см. мой блог: "Как сделать типизированные необязательные аргументы в C ++ 98" .

Приветствия & hth.,

0 голосов
/ 16 августа 2011

Загляните в библиотеку Boost.Parameters. Он использует шаблон тяжелой работы, чтобы заставить его работать. http://www.boost.org/doc/libs/1_37_0/libs/parameter/doc/html/index.html

Он работает в основном, превращая ваши именованные параметры в типы, которые можно создавать и назначать.

0 голосов
/ 16 августа 2011

Смешивание C ++ 0x и немного Boost (последний из которых вы могли бы легко заменить):

typedef boost::variant<A,  B, C> Arg;
void f(std::initializer_list<Arg> args)
{
    A *a = NULL;
    B *b = NULL;
    C *c = NULL;

    // for each arg, assign to a, b, or c,
    // possibly throwing if one type is used multiple times

    if (!a)
        a = new A();
    // and similar for B and C, or use smarter pointer types

    f(*a, *b, *c);
}

Вызывается так, что только немного страшнее, чем должно быть:

f({C(), B()});

Я не проверял это, но думаю, что это может сработать.

...