2013-06-15 12 views
10

Próbowałem napisać bezpieczną dla wątku owijkę dla std :: cout i uznałem, że to dobry moment, aby nauczyć się kilku szablonów variadic.Czy jest jakaś sztuczka, która pozwoliłaby mi przekazywać manipulatory strumieniowe do funkcji szablonu variadic?

Like that.

Wtedy, kiedy myślałem, że miał rację, zauważyłem, że nie działa z std :: endl.

Weź ten kod:

template <typename... P> 
void f(P...){} 

int main() 
{ 
    f(1,2,3,std::endl); 
} 

Podczas próby go skompilować, GCC narzeka w bardzo głupi sposób:

main.cpp:18:19: error: too many arguments to function 'void f(P ...) [with P = {}]' 

Podczas próby, że przy regularnym szablonu, masz

main.cpp:22:13: error: no matching function for call to 'f(<unresolved overloaded function type>)' 

który ma sens.

To nie jest duży problem dla mnie, mogę to zrobić w jakiś inny sposób, ale naprawdę chciałbym wiedzieć, czy jest jakiś sposób obejść to ograniczenie.

Odpowiedz

10

Problem polega na tym, że manipulatory takie jak std::endl są szablonami funkcji. Dlatego musisz jasno określić, która specjalizacja tego szablonu funkcji chcesz przekazać (w przeciwnym razie nie jest możliwe odliczanie typu).

Na przykład:

f(1, 2, 3, &std::endl<char, std::char_traits<char>>); 
//     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
14

Zamiast jednoznacznych argumentów szablonu jak sugeruje Andy Prowl, polecam szablonu odliczenie argumentu:

C:\Temp>type meow.cpp 
#include <iostream> 
#include <utility> 
using namespace std; 

void Print() { } 

template <typename T, typename... Rest> void Print(T&& t, Rest&&... rest) { 
    cout << forward<T>(t); 
    Print(forward<Rest>(rest)...); 
} 

int main() { 
    ostream& (* const narrow_endl)(ostream&) = endl; 

    Print("Hello, world!", narrow_endl, "I have ", 1729, " cute fluffy kittens.", 
     static_cast<ostream& (*)(ostream&)>(endl) 
    ); 
} 

C:\Temp>cl /EHsc /nologo /W4 /MTd meow.cpp 
meow.cpp 

C:\Temp>meow 
Hello, world! 
I have 1729 cute fluffy kittens. 

N3690 13.4 [over.over] określa zasady bycia używane tutaj, które powracają do C++ 98. Zasadniczo przyjmowanie adresu przeciążonej i/lub szablonowej funkcji jest ogólnie niejednoznaczne, ale dozwolone w określonych kontekstach. Inicjalizacja i static_casting to dwa z tych kontekstów, ponieważ dostarczają wystarczającej informacji o typie do ujednoznacznienia. Umożliwia to normalne odliczanie argumentów szablonu.

Jawne argumenty w szablonach są bardzo kuszące, ale mogą eksplodować na różne sposoby. Jest mało prawdopodobne, aby std :: endl kiedykolwiek został zmieniony w sposób, który łamie jawne argumenty z szablonu tutaj, ale naprawdę polecam przeciwko nim (z wyjątkiem sytuacji, gdy rzeczy są specjalnie zaprojektowane dla nich, jak forward i make_shared).

+0

Więc nie ma sposobu, aby napisać coś, co pozwoliłoby użytkownikowi używać modyfikatorów według ich zwykłych nazw, zamiast takich rzeczy jak narrow_endl? – user697683

+0

@ user697683 jeśli zamierzasz napisać funkcję drukowania owijając ostream, możesz równie dobrze wprowadzić funkcje wrapper do formatowania (tj. Nie zezwalaj na używanie manipulatorów ostream w interfejsie i dostarczaj alternatyw). Ale wtedy skończyłoby się na tym, że printf został zaimplementowany na szczycie ostream, który najprawdopodobniej został zaimplementowany na szczycie C printf. – rubenvb

2

template funkcje nie są funkcjami, a std::endl jest funkcją . Nie można przekazać funkcji template dookoła. Można jednak przekazać obiekt funkcji reprezentujący zestaw przeciążeniowy.Pisanie takiego funktora jest całkiem proste:

struct endl_overloadset { 
    template<typename... Args> 
    auto operator()(Args&&...args)const 
    ->decltype(std::endl(std::forward<Args>(args))) 
    { return (std::endl(std::forward<Args>(args))) }; 

    template<typename T,typename=typename std::enable_if<\ 
    std::is_same< decltype(static_cast<T>(std::endl)), T >::value\ 
    >::type>\ 
    operator T() const { return std::endl; } 
}; 

ale uważam, że jest to trochę za dużo jak boilerplate, więc napisać kilka makr, które wykona pracę za Ciebie:

#define RETURNS(X) ->decltype(X) { return (X); } // C++11 lacks non-lambda return value deduction 
#define OVERLOAD_SET(F) struct {\ 
    template<typename... Args>\ 
    auto operator()(Args&&...args)const\ 
    RETURNS(F(std::forward<Args>(args)...))\ 
    template<typename T,typename=typename std::enable_if<\ 
    std::is_same< decltype(static_cast<T>(F)), T >::value\ 
    >::type>\ 
    operator T() const { return F; }\ 
} 
static OVERLOAD_SET(std::endl) Endl; 

następnie przekazać Endl do twojego f, a wywołanie Endl(Blah) kończy się robi std::endl(Blah). Podobnie przypisanie Endl do zmiennej lub przekazanie jej do metody zasadniczo powoduje to samo, co przypisanie std::endl do zmiennej lub przekazanie jej do metody (rozdzielczość przeciążenia Wrt).

Niestety, OVERLOAD_SET nie może być używane w ramach funkcji, ponieważ typy lokalne nie mogą mieć metod template. Jeśli można go użyć w funkcji, to:

f(1,2,3, OVERLOAD_SET(std::endl)()); 

zrobi to, co chcesz. Ale to jest język, w którym chcesz programować, a nie język, który masz. (Jeszcze lepiej byłoby, gdyby propozycja @ Xeo pozwalała na automatyczne generowanie funktorów przeciążenia za pomocą losowego dalszego nadużywania składni, zamiast polegania na makrach).

Live example, gdzie mogę przekazać moje endl_functor się do print metodę, a następnie użyć << na nim bez zbędnych ceregieli.

+0

Zauważ, że takiego przekształconego manipulatora nie można już używać jak prawdziwego: 'std :: cout << OVERLOAD_SET (std :: endl) {};' (lub nawet 'std :: cout << [] std: : endl; ') nie skompiluje się, ponieważ te dwa nie generują normalnych funkcji, lecz obiekty funkcji. – Xeo

+0

@xeo hmm: dodanie 'operatora T' może działać. – Yakk

+0

@xeo 'operator T' dodano i przetestowano! 'endl_functor' obecnie konwertuje na te same rzeczy, na które' std :: endl' się skonwertuje. – Yakk

Powiązane problemy