Как я могу четко указать, какие аргументы я передаю, а какие остаются по умолчанию?

Задано из-за этого: аргумент по умолчанию в c++

Скажем, у меня есть такая функция: void f(int p1=1, int p2=2, int p3=3, int p4=4);

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

Что-то вроде этого будет работать:

template<bool P1=true, bool P2=true, bool P3=true, bool P4=true>
void f(int p1=1, int p2=2, int p3=3, int p4=4);
// specialize:
template<>
void f<false, true, false, false>(int p1) {
  f(1, p1);
}
template<>
void f<false, true, true, false>(int p1, int p2) {
  f(1, p1, p2);
}
// ... and so on. 
// Would need a specialization for each combination of arguments
// which is very tedious and error-prone

// Use:
f<false, true, false, false>(5); // passes 5 as p2 argument

Но это требует слишком много кода, чтобы быть практичным.

Есть лучший способ сделать это?


person Pubby    schedule 18.11.2011    source источник
comment
чего именно вы хотите добиться? Я чувствую, что все, что вы пытаетесь сделать, можно сделать проще. приведенный выше код выглядит как начало кошмара обслуживания.   -  person AndersK    schedule 18.11.2011
comment
@АндерсК. Способ указать, какие аргументы я хочу использовать. Я согласен, что этого делать не следует, но мне все еще любопытно, возможно ли это.   -  person Pubby    schedule 18.11.2011
comment
Вам это действительно нужно? Вам придется рассмотреть (N!) вариантов расстановки аргументов. 4! = 24 определения. Совершенно проще передать все параметры   -  person Andrey Atapin    schedule 18.11.2011
comment
@ Андрей Атапин - это не n !, просто 2 ^ n, большая разница.   -  person Gene Bushuyev    schedule 18.11.2011
comment
Кстати. какой бы ни была причина для этого конкретного кода, подобные случаи действительно появляются. В таких случаях я пишу инструменты генерации кода C++ для их обработки, некоторые люди используют макропроцессор m4, некоторые используют макросы VS. Я лично предпочитаю генерацию кода макросам.   -  person Gene Bushuyev    schedule 18.11.2011
comment
@GeneBushuyev, мой плохой, совершенно верно - 2 ^ n. Хотя это все равно не смешно.   -  person Andrey Atapin    schedule 19.11.2011
comment
видимо была опечатка. исправлено.   -  person Johannes Schaub - litb    schedule 26.11.2011


Ответы (4)


Используйте идиому именованных параметров ( ссылка на часто задаваемые вопросы).

Библиотека Boost.Parameters ( ссылка) также может решить эту задачу, но за счет многословия кода и значительного снижения ясности. Он также не справляется с конструкторами. И, конечно же, для этого требуется установленная библиотека Boost.

person Cheers and hth. - Alf    schedule 18.11.2011
comment
Именованные параметры идиома только синтаксически отличается от набора функций установки. Последнее ИМХО более естественно, так как связанная функция вызывает плохой запах. Оба подхода имеют одну и ту же проблему, создавая функции типа init вместо инициализации ctor. - person Gene Bushuyev; 19.11.2011
comment
Имейте в виду, что Parameters выдает одни из худших сообщений об ошибках, которые вы когда-либо видели от компилятора, если вы используете его для кода шаблона. - person pmr; 27.11.2011

Взгляните на Boost.Parameter библиотека.

Он реализует именованные параметры в C++. Пример:

#include <boost/parameter/name.hpp>
#include <boost/parameter/preprocessor.hpp>
#include <iostream>

//Define
BOOST_PARAMETER_NAME(p1)    
BOOST_PARAMETER_NAME(p2)
BOOST_PARAMETER_NAME(p3)
BOOST_PARAMETER_NAME(p4)

BOOST_PARAMETER_FUNCTION(
                         (void),
                         f,
                         tag,
                         (optional            
                          (p1, *, 1)
                          (p2, *, 2)
                          (p3, *, 3)
                          (p4, *, 4)))
{
    std::cout << "p1: " << p1 
            << ", p2: " << p2
            << ", p3: " << p3
            << ", p4: " << p4 << "\n";
}
//Use
int main()
{
    //Prints "p1: 1, p2: 5, p3: 3, p4: 4"
    f(_p2=5);
}
person Mankarse    schedule 18.11.2011
comment
Мне очень нравится этот ответ, хотя он немного излишен. - person Pubby; 18.11.2011
comment
Boost.Parameters довольно избыточен для этой задачи. - person Cheers and hth. - Alf; 18.11.2011
comment
@Pubby: может быть, но интересно вникнуть в сгенерированный код (-E на gcc) и посмотреть, как они достигают этого с помощью метапрограммирования. Это одно из открытий в силу шаблонов. - person Matthieu M.; 18.11.2011
comment
@Matthieu: вы обнаружите, что часть метапрограммирования тривиальна, но чрезвычайно утомительна. и что это не все решается стандартным метапрограммированием языка, но также, для C++98, специфическими для компилятора причудами препроцессора. я думаю, что это один из величайших хаков в истории C++, но не то, что можно было бы использовать, кроме как в крайней необходимости: более разумные подходы, такие как NPI, гораздо проще и эффективнее работают с наиболее распространенными случаями. - person Cheers and hth. - Alf; 18.11.2011
comment
@AlfP.Steinbach: Я согласен с хаками препроцессора, поэтому я только (хотел) поговорить о шаблонах и порекомендовать посмотреть вывод препроцессора. Меня не поражает степень гениальности решения, больше сама идея. Это не то, о чем вы интуитивно думаете, когда смотрите на шаблоны, и все же это так элегантно использовать (но, как вы сказали, для настройки требуется некоторый шаблонный код). - person Matthieu M.; 18.11.2011

Хотя Boost.Parameters забавен, он страдает (к сожалению) от ряда проблем, среди которых коллизия заполнителей (и необходимость отлаживать причудливые ошибки препроцессоров/шаблонов):

BOOST_PARAMETER_NAME(p1)

Создаст заполнитель _p1, который вы затем будете использовать позже. Если у вас есть два разных заголовка, объявляющих один и тот же заполнитель, возникает конфликт. Не смешно.

Существует гораздо более простой (как концептуально, так и практически) ответ, основанный на шаблоне Builder, в некоторой степени — это Идиома именованных параметров.

Вместо указания такой функции:

void f(int a, int b, int c = 10, int d = 20);

Вы указываете структуру, в которой вы будете переопределять operator():

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

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

class f {
public:
  // Take mandatory arguments, set default values
  f(int a, int b): _a(a), _b(b), _c(10), _d(20) {}

  // Define setters for optional arguments
  // Remember the Chaining idiom
  f& c(int v) { _c = v; return *this; }
  f& d(int v) { _d = v; return *this; }

  // Finally define the invocation function
  void operator()() const;

private:
  int _a;
  int _b;
  int _c;
  int _d;
}; // class f

Вызов:

f(/*a=*/1, /*b=*/2).c(3)(); // the last () being to actually invoke the function

Я видел вариант с помещением обязательных аргументов в качестве параметров в operator(), это позволяет избежать сохранения аргументов в качестве атрибутов, но синтаксис немного страннее:

f().c(3)(/*a=*/1, /*b=*/2);

Как только компилятор встроит все вызовы конструкторов и сеттеров (поэтому они определены здесь, а operator() нет), это должно привести к такому же эффективному коду по сравнению с «обычным» вызовом функции.

person Matthieu M.    schedule 18.11.2011

Это не совсем ответ, но...

В Метапрограммирование шаблонов C++ Дэвида Абрахамса и Алексея Гуртового (опубликовано в 2004 г.! ) авторы говорят об этом:

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

f(slew = .799, name = "z");

Они продолжают говорить:

Мы не будем здесь вдаваться в подробности реализации этой библиотеки именованных параметров; это достаточно просто, поэтому мы предлагаем вам попробовать реализовать это самостоятельно в качестве упражнения.

Это было в контексте метапрограммирования шаблонов и Boost::MPL. Я не слишком уверен, как их «простая» реализация будет работать с параметрами по умолчанию, но я предполагаю, что она будет прозрачной.

person Keith Layne    schedule 18.11.2011
comment
На самом деле это сердце волшебства Boost.Parameters. Я также успешно использовал это для DSL, это действительно создает аккуратные интерфейсы. - person Matthieu M.; 18.11.2011
comment
@MatthieuM. Итак, все, что мне нужно сделать, это прочитать заголовки Boost.Parameters, чтобы изучить эту (черную?) магию? Пока я этим занимаюсь, я думаю, что прочитаю все исходники Boost. Тогда я смогу стать хорошим программистом на С++ к 70 годам. :) - person Keith Layne; 18.11.2011
comment
Честно говоря, не могу сказать, что хотелось бы использовать boost.parameters, вещь любопытная, но в реальных условиях лекарство хуже болезни. Я бы либо использовал генерацию кода, либо (если производительность не критична) простой анализатор нескольких строк и предоставил бы строку функции: f("slew = .799, name = \"z\""); - person Gene Bushuyev; 18.11.2011