2016-08-20 23 views
5

Mam szablon funkcji, który pobiera std::pair, a także wartość jednego z typów pary. Chciałbym wywołać tę funkcję za pomocą wpisu z std::map jako argumentu pair.Jak rozwiązać konflikt const/non-const w dedukcji argumentu szablonu

#include <map> 
#include <utility> 

template <typename T1, typename T2> 
void do_stuff(std::pair<T1, T2> const &pair, T1 const &val) { 
    // Imagine that this does something important... 
} 

int main() { 
    std::map<int, float> foo { { 0, 0.0 } }; 

    do_stuff(*foo.begin(), 0); 
} 

To nie skompilować, ponieważ typ wejścia mapie jest std::pair<const int, float>, więc odliczenie typ dla T1 ma sprzecznych rodzajów: const int poprzez pair argument i int poprzez val argument.

test.cc: In function ‘int main()’: 
test.cc:12:27: error: no matching function for call to ‘do_stuff(std::pair<const int, float>&, int)’ 
    do_stuff(*foo.begin(), 0); 
         ^
test.cc:5:6: note: candidate: template<class T1, class T2> void do_stuff(const std::pair<_T1, _T2>&, const T1&) 
void do_stuff(std::pair<T1, T2> const &pair, T1 const &val) { 
     ^~~~~~~~ 
test.cc:5:6: note: template argument deduction/substitution failed: 
test.cc:12:27: note: deduced conflicting types for parameter ‘const T1’ (‘const int’ and ‘int’) 
    do_stuff(*foo.begin(), 0); 
         ^

Jaki jest najlepszy sposób rozwiązania tego konfliktu? Najlepiej byłoby, gdyby T1 został wydedukowany jako int, ale może być const int, jeśli jest to łatwiejsze do wdrożenia.

Odkryłem, że mogę rozwiązać problem za pomocą albo std::remove_const lub std::decay od typu parametru val:

void do_stuff(std::pair<T1, T2> const &pair, typename std::remove_const<T1>::type const &val) { 

ale nie wiem który z nich jest bardziej odpowiednie, lub jeżeli istnieje inne rozwiązanie, które byłoby lepsze.

+0

nie rozumiem ... dlaczego nie zdefiniujesz odpowiednio 'do_stuff()'; Mam na myśli 'szablon void do_stuff (std :: para const & pair, T1 const i val)'? – max66

+0

@ max66, Chcę, aby działała również z parami, których pierwszy typ * nie jest * const. (Ponieważ cały parametr jest przekazywany przez odniesienie do const, stałość elementów pary jest nieistotna: albo const lub non const powinno być dozwolone dla któregokolwiek z nich.) – Wyzard

+0

Czy próbowałeś zbudować konstrukcję na mapie? Mam na myśli 'std :: map foo {{0, 0.0}};' – mtb

Odpowiedz

5

Jednym rozwiązaniem jest użycie numeru std::add_const zamiast bezpośredniego użycia słowa kluczowego const.

obie poprzez szablon zapobiega typu odliczenia poprzez tego typu parametru:

#include <map> 
#include <type_traits> 
#include <utility> 

template< class T1, class T2 > 
void do_stuff(std::pair<T1, T2> const& pair, std::add_const_t<T1>& val) 
{ 
    // Imagine that this does something important... 
    (void) pair; (void) val; 
} 

auto main() 
    -> int 
{ 
    std::map<int, float> foo { { 0, 0.0f } }; 
    do_stuff(*foo.begin(), 0); 
} 
+4

"' auto main() -> int' "Wziąłeś to za daleko – Dani

+1

@Dani masz na myśli' int' jest jeden znak zbyt daleko od nazwy funkcji w swoim kodzie? –

+0

@ Cheersandhth.-Alf Więc jest teraz "auto main" w C++ 17? (http://stackoverflow.com/questions/17134975/will-automatic-return-type-deduction-work-for-main) – kfsone

2

Nie bardzo elegancki, ale przypuszczam, że można oddzielić T1 w dwóch różnych paramenters szablonów.

Coś

template <typename T1, typename T2, typename T3> 
void do_stuff(std::pair<T1, T2> const &pair, T3 const &val) { 
    // Imagine that this does something important... 
} 

Możesz dodać czek, poprzez std::enable_if_t, nałożyć korelację między T1 i T3; przykładem

template <typename T1, typename T2, typename T3, 
     typename = std::enable_if_t<std::is_same<std::remove_const_t<T1>, std::remove_const_t<T3>>::value>> 
void do_stuff(std::pair<T1, T2> const &pair, T3 const & val) { 
    // Imagine that this does something important... 
} 
+0

Rozwiązuje to błąd kompilacji, ale także pozwala wywołującemu przekazać zupełnie inny, niepowiązany typ. (Nadal będzie generował błąd kompilacji, jeśli 'T3' ​​jest niezgodne z' T1', ale błąd będzie pochodził gdzieś z implementacji funkcji, gdzie mniej prawdopodobne jest, że jest jasne, jaki jest obecny problem.) – Wyzard

+0

@Wyzard - możesz narzucić za pośrednictwem SFINAE, że 'T1' i' T3' są ('const' bardziej lub mniej) równe; brzydszy niż wcześniej, ale ... Zmieniono moją odpowiedź. – max66

+0

W 'typename = nazwa_pliku std :: enable_if_t' druga" nazwa_pliku "nie jest potrzebna, to jest punkt aliasu" _t ". – Oktalist

3
template<class T>struct identity{using type=T;}; 
template<class T>using no_deduce=typename identity<T>::type; 

Wrap drugi typ w no_deduce zablokować odliczenia.

template <typename T1, typename T2> 
void do_stuff(std::pair<T1, T2> const &pair, no_deduce<T1> const &val) { 
    // Imagine that this does something important... 
} 

To działa i jest jasne, dlaczego to robisz.

Teraz, być może warto zastanowić się, co chcesz zrobić, jeśli T1 to rodzaj odniesienia, a co const& robi w tej sprawie. Wyobraź sobie, że T1 jest int&, a następnie int& const& staje się tylko int&.

To może nie być to, czego chcesz.

Może to, co chcesz, czego tak naprawdę naprawdę chcesz, to:

template <typename T1, typename T2> 
void do_stuff(std::pair<T1, T2> const &pair, std::remove_reference_t<T1> const &val) { 
    // Imagine that this does something important... 
} 

Jeśli chcesz const& musisz zapomnieć o &, jeśli chcesz zapobiec val przed modyfikacją lepiej upewnić się, że jest const . Teraz nie marnuj cennego const&, remove_reference_t i będzie dobrze.

Jeśli chcesz poradzić sobie z volatile, musisz uzyskać z remove_volatile_t. Połącz je na zawsze w template<class T>using clean_t=std::remove_cv_t<remove_reference_t<T>>. Jeśli chcesz const& masz do

template <typename T1, typename T2> 
void do_stuff(std::pair<T1, T2> const &pair, clean_t<T1> const &val) { 
    // Imagine that this does something important... 
} 

Simply const& jest łatwe, ale tak właśnie jest.

Powiązane problemy