Как я могу точно указать, какие аргументы я передаю, а какие остаются по умолчанию? - PullRequest
8 голосов
/ 18 ноября 2011

Запрошено из-за этого : Аргумент по умолчанию в 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

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

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

Ответы [ 4 ]

11 голосов
/ 18 ноября 2011

Используйте идиому Именованных параметров (& rarr; FAQ FAQ ).

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

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

7 голосов
/ 18 ноября 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);
}
4 голосов
/ 18 ноября 2011

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

BOOST_PARAMETER_NAME(p1)

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

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

вместоуказав такую ​​функцию:

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

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

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

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

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

2 голосов
/ 18 ноября 2011

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

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...