2012-12-13 16 views
15

Próbuję utworzyć klasę z szablonem typu variadic, która zapewnia metodę dla każdej klasy w liście typów. Przykładem jest pokazany poniżej której tworzy metodę print dla każdej klasy w typelist:Niejednoznaczne wywołania funkcji do klas bazowych C++

#include <iostream> 
#include <string> 

// Helper class providing a function call 
template <typename T> 
class PrintHelper 
{ 
public: 
    void print(const T& t) { std::cout << t << std::endl; } 
}; 

// Provides a print method for each type listed 
template <typename... Ts> 
class Printer : public PrintHelper<Ts>... 
{}; 

int main() 
{ 
    Printer<int, std::string> p; 
    p.print(std::string("Hello World")); // Ambiguous Call 
} 

komentowanym wynikiem błędu w linii z GCC 4.6.3 na komentowanej linii. Jaki jest właściwy sposób rozwiązania tej dwuznaczności, czy powinienem spojrzeć na inny projekt?

+0

sam problem z gcc 4.7.2 (w [liveworkspace] (http://liveworkspace.org/code/1a4YN8$0)). Dziękujemy za skopiowanie/wklejenie przykładu btw. –

+1

klang 3.2 trunk 165721 mówi: 'error: member 'print' znaleziony w wielu klasach bazowych różnych typów' – user786653

+2

Zobacz także [to pytanie] (http://stackoverflow.com/questions/5368862/why-do-multiple-inherited -funkcje-z takimi samymi nazwami, ale różne podpisy-nie). Jest to w istocie problem kombinacji reguł ukrywania i bardzo ograniczonych możliwości rozpakowywania argumentów szablonu variadic. –

Odpowiedz

8

Nie lubię odpowiadać na moje własne pytanie, ale stworzyłem następujące rozwiązanie z komentarzy tutaj. Wprowadza wszystkie funkcje print do zakresu i pozwala na przeciążenie C++ dla wszystkich funkcji.

#include <iostream> 
#include <string> 

// Helper class providing a function call 
template <typename T> 
class PrintHelper 
{ 
public: 
    void print(const T& t) { std::cout << t << std::endl; } 
}; 

// Provides a print method for each type listed 
template <typename... Ts> 
class Printer 
{}; 

template<typename T> 
class Printer<T> : public PrintHelper<T> 
{ 
public: 
    using PrintHelper<T>::print; 
}; 

template<typename T, typename... Ts> 
class Printer<T, Ts...>: public PrintHelper<T>, public Printer<Ts...> 
{ 
public: 
    using PrintHelper<T>::print; 
    using Printer<Ts...>::print; 
}; 

int main() 
{ 
    Printer<int, std::string> p; 
    p.print("Hello World"); // Not an ambiguous Call 
} 
+0

+1 To wspaniale, dlaczego nie pomyślałem o tym ... Więc myślę, że w ten sposób można rozpakować deklaracje użycia dla szablonów variadic. –

+0

Całkiem dobrze jest odpowiedzieć na własne pytanie (a nawet wyraźnie zachęcić). Jest to korzystne dla przyszłych czytelników. Cf. http://blog.stackoverflow.com/2011/07/its-ok-to-ask-and-answer-your-own-questions/. Również proponowane rozwiązanie powinno być zaakceptowane jednym imho. –

9

Aby rozwiązać dwuznaczność, to jest możliwe do zrobienia

template <typename... Ts> 
struct Printer : PrintHelper<Ts>... 
{ 
    template <typename U> 
    void print (const U& t) 
    { 
     PrintHelper<U>::print (t); 
    } 
}; 

(patrz an example)

ale to nie jest tak wytrzymała, jak można by mieć nadzieję. W szczególności nie można wydrukować obiektu, który można przekształcić na jeden z typów z listy typów.

W przypadku niektórych metaprogramowania szablonów możliwe jest jednak wysłanie do właściwej drukarki. Aby to zrobić, musisz wybrać typ z Ts..., na który można dokonać konwersji U, i zadzwonić pod właściwy numer PrintHelper, tj.

PrintHelper<typename find_convertible<U, Ts...>::type>::print (t); 

gdzie find_convertible<U, Ts...> jest zdefiniowany przez

template <typename U, typename... Ts> 
struct find_convertible 
{}; 

template <typename U, typename V, typename... Ts> 
struct find_convertible<U, V, Ts...> : 
    std::conditional< 
     std::is_convertible<U, V>::value, 
     std::common_type<V>, // Aka identity 
     find_convertible<U, Ts...> 
    >::type 
{}; 

(patrz example)

+0

Piękny spokój kodu! –

+0

Dziękuję za pomoc. Pierwszy przykład jest wystarczający dla mojego przypadku użycia. Podążając za jednym z powyższych komentarzy, istnieje inne potencjalne rozwiązanie pozwalające na konwersje poprzez zmianę dziedziczenia (http://liveworkspace.org/code/2PpiGS$1). Użyje to przeciążeniowej rozdzielczości C++ w przeciwieństwie do twojej metody, która zajmie pierwszy typ wymienialny w liście typów. –

+0

@PeterOgden: Naprawdę lubię twoją inną metodę. Czy możesz napisać to jako odpowiedź na przyszłość? Pastuchy online nie trwają wiecznie. –

3

Poniższy kod może rozwiązać ten problem niejednoznaczności:

#include <iostream> 
#include <string> 

// Helper class providing a function call 
template <typename T> 
class PrintHelper 
{ 
    protected: 
    void print_impl(const T& t) { std::cout << t << std::endl; } 
}; 

// Provides a print method for each type listed 
template <typename... Ts> 
class Printer : public PrintHelper<Ts>... 
{ 
    public: 
    template <typename U> 
    void print(const U& u) { 
     PrintHelper<U>::print_impl(u); 
    }; 
}; 

int main() 
{ 
    Printer<int, std::string> p; 
    p.print(std::string("Hello World")); // Ambiguous Call 
} 

co nie jest bardzo dobre, bo od wymóg, że typ U (wydedukowany podczas rozmowy) jest dokładnie jednym z nich e typy na liście typów variadic. Być może uda ci się trochę poprawić sytuację, aby rozwiązać ten problem. Przy odrobinie szablonowej magii i Sfinae, prawdopodobnie możesz rozwiązać ten problem dość łatwo (ale na pewno nie jest tak schludny i czysty).

Problem niejednoznaczności nie jest związany z wykorzystaniem szablonów lub szablonów variadic, oczywiście jest to prosta aplikacja do reguł wyszukiwania członków (sekcja 10.2/2 normy), tj. Często nazywana "zasady ukrywania członków". Jeśli wziąć tę prostszą wersję non-szablon, otrzymasz ten sam problem dwuznaczności, ale z bardzo prostego rozwiązania IT:

struct IntPrinter { 
    void print(const int& i) { std::cout << i << std::endl; }; 
}; 

struct StringPrinter { 
    void print(const std::string& s) { std::cout << s << std::endl; }; 
}; 

struct IntStringPrinter : IntPrinter, StringPrinter { 
    using IntPrinter::print;  // These using-statements will solve the problem 
    using StringPrinter::print; // by importing all 'print' functions to the same 
           // overload resolution level. 
}; 

Więc problem tutaj jest naprawdę, że dwuznaczność powstaje przed kompilator nawet prób aby zastosować normalne reguły dotyczące przeciążania, ponieważ najpierw próbuje ustalić, która gałąź dziedziczenia ma zostać znaleziona, aby znaleźć funkcję (funkcje) członka, a następnie rozwiąże przeciążenie tylko na jednym poziomie dziedziczenia. Problemem podczas korzystania z szablonów variadic jest to, że nie istnieje żaden sposób rozpakowania zestawu instrukcji "using" w celu zaimportowania wszystkich funkcji drukowania do tego samego poziomu dziedziczenia. Jeśli ktokolwiek wie, jak rozpakować takie instrukcje użycia, jestem uszy. Być może będziesz musiał wrócić do rozwiązań sprzed-variadic-template (jak szablon ogólny z 10 argumentami i specjalizować się we wszystkich dziesięciu wersjach).

Powiązane problemy