Указание параметра по умолчанию при вызове функции C ++

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

void f(int a = 0, int b = 0, int c = 0)
{
    //...Some Code...
}

Как вы можете видеть выше с моим кодом, параметры _2 _, _ 3_ и c имеют значения параметров по умолчанию 0. Теперь взгляните на мою основную функцию ниже:

int main()
{
   //Here are 4 ways of calling the above function:
   int a = 2;
   int b = 3;
   int c = -1;

   f(a, b, c);
   f(a, b);
   f(a); 
   f();
   //note the above parameters could be changed for the other variables
   //as well.
}

Теперь я знаю, что я не могу просто пропустить параметр и позволить ему иметь значение по умолчанию, потому что это значение будет оцениваться как параметр в этой позиции. Я имею в виду, что я не могу, скажем, позвонить, f(a,c), потому что c будет оцениваться как b, чего я не хочу, особенно если c - неправильный тип. Есть ли способ указать вызывающей функции на C ++, чтобы использовать любое значение параметра по умолчанию для функции в любой заданной позиции, не ограничиваясь переходом назад от последнего параметра к отсутствию? Есть ли какое-либо зарезервированное ключевое слово для этого или, по крайней мере, обходной путь? Я могу привести такой пример:

f(a, def, c) //Where def would mean default.

person Arnav Borborah    schedule 06.08.2016    source источник
comment
Вы можете посмотреть именованные параметры. Есть несколько приемов, позволяющих использовать эту функцию в C ++ как BOOST_PARAMETER_FUNCTION, а затем указать, какой параметр вы задаете.   -  person Jarod42    schedule 07.08.2016
comment
Если кажется, что вам нужно это сделать, у вас может быть недостаток дизайна. Предлагаю вам переоценить это.   -  person Rob K    schedule 28.09.2016
comment
@RobK Это был вопрос любопытства.   -  person Arnav Borborah    schedule 17.10.2016


Ответы (5)


В качестве обходного пути вы можете (ab) использовать boost::optional (до std::optional из c ++ 17):

void f(boost::optional<int> oa = boost::none,
       boost::optional<int> ob = boost::none,
       boost::optional<int> oc = boost::none)
{
    int a = oa.value_or(0); // Real default value go here
    int b = ob.value_or(0); // Real default value go here
    int c = oc.value_or(0); // Real default value go here

    //...Some Code...
}

а затем назовите это

f(a, boost::none, c);
person Jarod42    schedule 06.08.2016
comment
Это хорошее оптимальное решение. Мне это действительно нравится, и я могу этим воспользоваться. +1 - person Arnav Borborah; 07.08.2016

Для этого нет зарезервированного слова, и f(a,,c) тоже недействителен. Вы можете опустить ряд крайних правых необязательных параметров, как вы показываете, но не средний, как это.

http://www.learncpp.com/cpp-tutorial/77-default-parameters/

Цитата прямо по приведенной выше ссылке:

Несколько параметров по умолчанию

Функция может иметь несколько параметров по умолчанию:

void printValues(int x=10, int y=20, int z=30)
{
    std::cout << "Values: " << x << " " << y << " " << z << '\n';
}

Учитывая следующие вызовы функций:

printValues(1, 2, 3);
printValues(1, 2);
printValues(1);
printValues();

Будет произведен следующий вывод:

Values: 1 2 3
Values: 1 2 30
Values: 1 20 30
Values: 10 20 30

Обратите внимание, что невозможно предоставить определяемое пользователем значение для z, не указав также значения для x и y. Это связано с тем, что C ++ не поддерживает синтаксис вызова функции, такой как printValues ​​(,, 3). Это имеет два основных последствия:

1) Все параметры по умолчанию должны быть крайними правыми параметрами. Запрещено следующее:

void printValue(int x=10, int y); // not allowed

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

person Alejandro    schedule 06.08.2016
comment
Я уже читал вашу ссылку, но я уже знаю, что вы говорите прямо сейчас. Тем не менее, спасибо за ваш вклад, и если лучший ответ не появится, я отмечу его как правильный. - person Arnav Borborah; 06.08.2016
comment
Ok. если вы верите в то, что он говорит, то на ваш вопрос ответят. вам необходимо структурировать свою функцию, чтобы обеспечить желаемое поведение. Например, если пользователь вводит какое-то недопустимое значение, например -1, то он должен использовать значение по умолчанию. - person Alejandro; 06.08.2016

Не совсем то, что вы просили, но вы можете использовать std::bind(), чтобы исправить значение параметра.

Что-то вроде

#include <functional>

void f(int a = 0, int b = 0, int c = 0)
{
    //...Some Code...
}

int main()
{
   // Here are 4 ways of calling the above function:
   int a = 2;
   int b = 3;
   int c = -1;

   f(a, b, c);
   f(a, b);
   f(a); 
   f();
   // note the above parameters could be changed 
   // for the other variables as well.

   using namespace std::placeholders;  // for _1, _2

   auto f1 = std::bind(f, _1, 0, _2);

   f1(a, c); // call f(a, 0, c);

   return 0;
}

С помощью std::bind() вы можете исправить значения, отличные от значений параметров по умолчанию, или значения для параметров без значений по умолчанию.

Учтите, что std::bind() доступен только с C ++ 11.

person max66    schedule 06.08.2016
comment
Спасибо за обходной путь. Я действительно мог бы использовать это. +1 - person Arnav Borborah; 06.08.2016
comment
@ArnavBorborah - пожалуйста; изменил свой ответ рядом с вашим комментарием; надеясь, что это поможет. - person max66; 06.08.2016

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

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

#include <tuple>
#include <iostream>
#include <type_traits>

// -----
// from http://stackoverflow.com/a/25958302/678093
template <typename T, typename Tuple>
struct has_type;

template <typename T>
struct has_type<T, std::tuple<>> : std::false_type {};

template <typename T, typename U, typename... Ts>
struct has_type<T, std::tuple<U, Ts...>> : has_type<T, std::tuple<Ts...>> {};

template <typename T, typename... Ts>
struct has_type<T, std::tuple<T, Ts...>> : std::true_type {};

template <typename T, typename Tuple>
using tuple_contains_type = typename has_type<T, Tuple>::type;
//------


template <typename Tag, typename T, T def>
struct Value{
    Value() : v(def){}
    Value(T v) : v(v){}
    T v; 
};

using A = Value<struct A_, int, 1>;
using B = Value<struct B_, int, 2>;
using C = Value<struct C_, int, 3>;


template <typename T, typename Tuple>
std::enable_if_t<tuple_contains_type<T, Tuple>::value, T> getValueOrDefaultImpl(Tuple t)
{
    return std::get<T>(t);
}

template <typename T, typename Tuple>
std::enable_if_t<!tuple_contains_type<T, Tuple>::value, T> getValueOrDefaultImpl(Tuple)
{
    return T{};
}

template <typename InputTuple, typename... Params>
auto getValueOrDefault(std::tuple<Params...>, InputTuple t)
{
    return std::make_tuple(getValueOrDefaultImpl<Params>(t)...);
}

template <typename... Params, typename ArgTuple>
auto getParams(ArgTuple argTuple) 
{
    using ParamTuple = std::tuple<Params...>;
    ParamTuple allValues = getValueOrDefault(ParamTuple{}, argTuple);
    return allValues;
}

template <typename... Args>
void f(Args ... args)
{
    auto allParams = getParams<A,B,C>(std::make_tuple(args...));
    std::cout << "a = " << std::get<A>(allParams).v << " b = " << std::get<B>(allParams).v << " c = " << std::get<C>(allParams).v << std::endl;
}

int main()
{
   A a{10};
   B b{100};
   C c{1000};

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

вывод

a = 10 b = 100 c = 1000
a = 10 b = 100 c = 1000
a = 10 b = 100 c = 3
a = 10 b = 2 c = 3
a = 1 b = 2 c = 3

живой пример

person m.s.    schedule 06.08.2016
comment
Спасибо за ответ, но вариативные шаблоны .... +1, в любом случае, спасибо за ссылку - person Arnav Borborah; 06.08.2016
comment
@ArnavBorborah что не так с вариативными шаблонами? - person m.s.; 06.08.2016
comment
Разве многоточие небезопасно? - person Arnav Borborah; 06.08.2016
comment
@ArnavBorborah каким образом небезопасно? кстати: вариативный шаблон не то же самое, что многоточие C. - person m.s.; 06.08.2016
comment
О, хорошо, я их сбивал с толку. Другой ответ о переполнении стека упоминал, что c - многоточие опасно. - person Arnav Borborah; 06.08.2016

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

Вы можете строго ввести аргументы:

struct A { int value = 0; };
struct B { int value = 2; };
struct C { int value = 4; };

void f(A a = {}, B b = {}, C c = {}) {}
void f(A a, C c) {}

int main()
{
    auto a = 0;
    auto b = -5;
    auto c = 1;

    f(a, b, c);
    f(a, C{2});
    f({}, {}, 3);
}

Преимущества:

  • это просто и легко поддерживать (одна строка на аргумент).
  • обеспечивает естественную точку для дальнейшего ограничения API (например, throw, если значение B отрицательное).
  • он не мешает (работает с конструкцией по умолчанию, работает с intellisense / автозаполнением / чем угодно так же хорошо, как и любой другой класс)
  • это самодокументируется.
  • он такой же быстрый, как и родная версия.

Недостатки:

  • увеличивает загрязнение имени (лучше поместить все это в пространство имен).
  • хотя это просто, это еще больше кода, который нужно поддерживать (чем просто определение функции напрямую).
  • это может вызвать недоумение (подумайте о добавлении комментария о том, зачем нужна строгая типизация)
person utnapistim    schedule 28.09.2016