2016-08-06 28 views
9

Załóżmy, że mam kod jak poniżej:Określanie domyślnego parametru podczas wywoływania C++ funkcji

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

Jak można wyraźnie zobaczyć powyżej, z mojego kodu, parametry a, b, a c mają domyślne wartości parametrów 0. Teraz spójrz na moją główną funkcją poniżej:

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. 
} 

teraz wiem, że nie można po prostu pominąć parametr, i niech on mieć wartość domyślną, ponieważ ta wartość będzie oceniać jako parametr w tej pozycji. Mam na myśli to, że nie mogę, powiedzmy, zadzwonić, f(a,c), ponieważ, c byłby oceniany jako b, czego nie chcę, zwłaszcza jeśli c jest niewłaściwym typem. Czy istnieje sposób, aby funkcja wywołująca określiła w C++, aby użyć jakiejkolwiek domyślnej wartości parametru dla funkcji w danej pozycji, bez ograniczania się do cofania od ostatniego parametru do żadnego? Czy istnieje jakieś zastrzeżone słowo kluczowe, aby to osiągnąć, lub przynajmniej obejść? Przykładem może dam byłoby jak:

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

Można spojrzeć na wymienionych parametrów. Istnieje kilka sztuczek, które mają tę funkcję w C++ jako BOOST_PARAMETER_FUNCTION, a następnie określ, który parametr dajesz. – Jarod42

+0

Jeśli wydaje ci się, że musisz to zrobić, możesz mieć wadę projektową. Sugeruję, żebyś to ponownie ocenił. –

+0

@RobK To była tylko kwestia ciekawości. –

Odpowiedz

6

Jako obejście, można (ab) używać boost::optional (aż std::optional z 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... 
} 

a następnie nazwać

f(a, boost::none, c); 
+1

To jest dobre optymalne rozwiązanie. Tak naprawdę to lubię i mogę z niego korzystać. +1 –

12

Nie jest słowem zarezerwowanym dla tego, a f(a,,c) jest nieprawidłowy albo. Możesz pominąć pewną liczbę parametrów opcjonalnych po prawej stronie, jak to pokazujesz, ale nie w środkowym.

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

Cytując bezpośrednio z linku powyżej:

wielu parametrów domyślnych

Funkcja może mieć wiele domyślne parametry:

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

otrzymuje następujące funkcje połączenia:

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

Poniżej przedstawiono wyniki wymienić:

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

Należy zauważyć, że nie jest możliwe, aby dostarczyć wartości zdefiniowanych przez użytkownika dla Z bez jednoczesnego dostarczania wartości dla x i y. Dzieje się tak dlatego, że C++ nie obsługuje składni wywołania funkcji, takiej jak printValues ​​(,, 3). Ma to następujące ważne konsekwencje:

1) Wszystkie parametry domyślne muszą być parametrami skrajnymi po prawej stronie. Poniżej jest niedozwolone: ​​

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

2) Jeśli więcej niż jeden domyślny parametr istnieje, od lewej domyślny parametr powinien być jeden najprawdopodobniej być jawnie ustawione przez użytkownika .

+4

Już przeczytałem twój link, ale już wiem, co mówisz teraz. Dziękujemy za twoje zgłoszenie, a jeśli lepsza odpowiedź się nie pojawi, zaznaczę to jako poprawne. –

+0

ok. jeśli wierzysz w to, co mówi, twoje pytanie zostanie wysłuchane. musisz uporządkować swoją funkcję, aby włączyć pożądane zachowanie. Na przykład, jeśli użytkownik wprowadzi jakąś nieprawidłową wartość, być może -1, wówczas użyje wartości domyślnej. – Alejandro

+1

przypadkowo wciśnięty enter. komentarz edytowany – Alejandro

5

Nie dokładnie to, o co prosisz, ale możesz użyć wartości std::bind(), aby poprawić wartość parametru.

somethink jak

#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; 
} 

Z std::bind() można ustalić wartości różne od domyślnych wartości parametrów lub wartości parametrów bez wartości domyślnych.

Przyjmij tylko, że std::bind() jest dostępny tylko z C++ 11.

p.s .: przepraszam za mój zły angielski.

+1

Dziękujemy za obejście tego problemu. Właściwie mogę tego użyć. +1 –

+0

@ArnavBorborah - nie ma za co; zmodyfikowałem moją odpowiedź obok twojego komentarza; mając nadzieję, że to pomaga. – max66

1

Jeśli wszystkie parametry funkcji były odrębne typy, można dowiedzieć się, które parametry zostały przekazane, a które nie były i wybierz wartość domyślną dla tych ostatnich.

Aby uzyskać różne wymagania, można zawijać parametry i przekazywać je do szablonu funkcji variadic. Wtedy nawet kolejność argument już nie ma znaczenia:

#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(); 
} 

wyjście

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 

live example

+1

Dziękuję za odpowiedź, ale szablony variadic .... +1, tak, i dzięki za link –

+0

@ArnavBorborah, co jest nie tak z szablonami variadic? –

+1

Czy elipsa nie jest niebezpieczna? –

0

edit: ta kwestia była starsza i znalazłem go, gdy inna sprawa została zamknięta w dwóch egzemplarzach (dla plagiat).

Masz już zaakceptowane odpowiedź, ale oto kolejny obejście (które - wierzę - ma przewagę nad innymi proponowanymi obejściach):

Można silny typu argumenty:

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); 
} 

Zalety :

  • Jest to proste i łatwe do utrzymania (jedna linia na argument).
  • zapewnia naturalny punkt do dalszego ograniczania interfejsu API (na przykład "wyrzuć, jeśli wartość B jest ujemna").
  • to nie przeszkadza (działa z domyślną konstrukcją, działa z intellisense/auto-complete/cokolwiek tak dobrze, jak każda inna klasa)
  • to jest self-documenting.
  • jest tak szybki, jak wersja natywna.

Wady:

  • zwiększa zanieczyszczenie nazwisko (lepiej umieścić to wszystko w przestrzeni nazw).
  • Podczas gdy proste, nadal jest więcej kodu do utrzymania (niż tylko bezpośrednie zdefiniowanie funkcji).
  • może podnieść kilka brwi (warto dodać komentarz, dlaczego potrzebne jest silne typowanie)