2012-06-26 17 views
6

Piszę klasę, która ma kilka różnych funkcji z std::function (a przynajmniej klasy są pod wieloma względami podobne). Jak wszyscy wiecie, std::function tworzy instancję, określając parametry szablonu (tj. std::function<void (std::string&)>), jest taka sama dla mojej klasy. Mam jednak wyjątek, chcę specjalizować w klasie jedną funkcję, jeśli zwrócona wartość jest nieważna (std::function<"return value" ("parameters">). Muszę to zrobić w czasie kompilacji i po prostu nie mogę sprawić, żeby działało tak, jak powinno. Oto niektóre kodu testu dla wyjaśnienia:Specjalizacja typu w czasie kompilacji

#include <iostream> 
#include <type_traits> 

template <typename T> class Test { }; 

template <typename Ret, typename... Args> 
class Test<Ret (Args...)> 
{ 
public: 
    Ret operator()(Args...) 
    { 
     if(std::is_void<Ret>::value) 
     { 
      // Do something... 
     } 

     else /* Not a void function */ 
     { 
      Ret returnVal; 
      return returnVal; 
     } 
    } 
}; 

int main(int argc, char * argv[]) 
{ 
    Test<void (char)> test; 
    test('k'); 
} 

Jak można wyraźnie zobaczyć, jeśli kompilator nie usunąć „innego” oddział w powyższym teście, mój kod będzie próbował utworzyć wartość void (tj void returnVal;) . Problemem jest to, że kompilator nie usunąć gałąź więc skończyć z błędu kompilatora:

./test.cpp: In instantiation of ‘Ret Test::operator()(Args ...) [with Ret = void; Args = {char}]’: ./test.cpp:27:10: required from here ./test.cpp:18:8: error: variable or field ‘returnVal’ declared void ./test.cpp:19:11: error: return-statement with a value, in function returning 'void' [-fpermissive]

Jeden normalnie używać std::enable_if połączeniu z std::is_void, problemem jest to, że nie chcą się specjalizować na szablon funkcji, ale w szablonie klasy.

template <typename Ret, typename... Args> 
class Test<Ret (Args...)> 
{ 
public: 
    typename std::enable_if<!std::is_void<Ret>::value, Ret>::type 
    Ret operator()(Args...) 
    { 
     Ret returnVal; 
     return returnVal; 
    } 

    typename std::enable_if<std::is_void<Ret>::value, Ret>::type 
    Ret operator()(Args...) 
    { 
     // It's a void function 
     // ... 
    } 
}; 

Jeśli używam powyższy kod zamiast skończyć z jeszcze większej liczby błędów i bez rozwiązania

./test.cpp:11:2: error: expected ‘;’ at end of member declaration 
./test.cpp:11:2: error: declaration of ‘typename std::enable_if<(! std::is_void<_Tp>::value), Ret>::type Test<Ret(Args ...)>::Ret’ 
./test.cpp:6:11: error: shadows template parm ‘class Ret’ 
./test.cpp:11:24: error: ISO C++ forbids declaration of ‘operator()’ with no type [-fpermissive] 
./test.cpp:18:2: error: expected ‘;’ at end of member declaration 
./test.cpp:18:2: error: declaration of ‘typename std::enable_if<std::is_void<_Tp>::value, Ret>::type Test<Ret(Args ...)>::Ret’ 
./test.cpp:6:11: error: shadows template parm ‘class Ret’ 
./test.cpp:18:24: error: ISO C++ forbids declaration of ‘operator()’ with no type [-fpermissive] 
./test.cpp:18:6: error: ‘int Test<Ret(Args ...)>::operator()(Args ...)’ cannot be overloaded 
./test.cpp:11:6: error: with ‘int Test<Ret(Args ...)>::operator()(Args ...)’ 
./test.cpp: In member function ‘int Test<Ret(Args ...)>::operator()(Args ...)’: 
./test.cpp:22:2: warning: no return statement in function returning non-void [-Wreturn-type] 
./test.cpp: In instantiation of ‘int Test<Ret(Args ...)>::operator()(Args ...) [with Ret = void; Args = {char}]’: 
./test.cpp:28:10: required from here 
./test.cpp:13:7: error: variable or field ‘returnVal’ declared void 
./test.cpp: In member function ‘int Test<Ret(Args ...)>::operator()(Args ...) [with Ret = void; Args = {char}]’: 
./test.cpp:15:2: warning: control reaches end of non-void function [-Wreturn-type] 

Przepraszam, jeśli jestem po prostu głupi, a odpowiedź jest oczywista. Jestem całkiem nowy w szablonach i nie mogłem znaleźć odpowiadającej odpowiedzi w żadnym z pozostałych wątków/pytań.

+0

Istnieje propozycja włączenia "statycznego jeśli", który zrobiłby dokładnie to, co chcesz w swoim pierwszym przykładzie. – mfontanini

+0

@mfontanini: Niezależnie od tego, czy któraś z dwóch propozycji faktycznie się rozwija, to inna sprawa. Mają niedociągnięcia, a niektóre z nich stają się o wiele bardziej skomplikowane, niż wyglądają, jeśli zaczniesz używać SFINAE z szablonami, które mają statyczny-jeśli jest –

+0

"Problem polega na tym, że kompilator nie usuwa gałęzi, więc kończę z błędem kompilatora" = > Niestety, kompilator * jest wymagany, aby nie *, co (przyznaję) jest nieco denerwujące. –

Odpowiedz

7

Jest kilka rzeczy, które nie są dokładnie jasne z twojego opisu, więc zacznę od najbardziej ogólnej odpowiedzi.

Zakładając, że szablon ma inne funkcje, których zachowanie musi być takie samo, a chcesz tylko przedefiniować zachowanie dla tej konkretnej funkcji, najprostszą odpowiedzią jest podzielenie szablonu na dwie części i użycie dziedziczenia do scalenia im. W tym momencie można użyć częściową specjalizację szablonu na szablonie bazowym:

template <typename T, typename... Args> 
struct tmpl_base { 
    T operator()(Args... args) { 
     //generic 
    } 
}; 
template <typename... Args> 
struct tmpl_base<void,Args...> { 
    void operator()(Args... args) { 
    } 
}; 

template <typename Ret, typename... Args> 
class Test<Ret (Args...)> : tmp_base<Ret,Args...> { 
    // do not declare/define operator(), maybe bring the definition into scope: 
    using tmp_base<Ret,Args...>::operator(); 

    // Rest of the class 

Jeśli jest to jedyna funkcja w szablonie, a następnie częściowej specjalizacji jest znacznie prostsze rozwiązanie, które nie wymaga nadużywanie dziedziczenia.

+0

Zasadniczo odradzam używanie dziedziczenia, gdy kompozycja działa (lub tutaj, delegowanie do bezpłatnej funkcji), ale muszę przyznać, że prawdopodobnie nie wymyśliłbym niczego tak zwięzłego. –

+0

@ MatthieuM.: To chyba jedyne nadużycie spadku, które uważam za zło konieczne. Nie sądzę, aby w tym przypadku można było tutaj wykonać jakąkolwiek delegację, ponieważ głównym problemem jest to, że wyrażenie "return delegate (args ...)" będzie źle sformułowane dla przypadków, w których zwracane jest 'void' . Ten szczególny problem blokuje delegowanie do złożonego obiektu lub innej funkcji. –

+0

Chyba będziemy musieli to zrobić. To smutne, ile kodu jest wymagane do wykonania tak prostego zadania. Żałuję, że C++ nie ma [static if] (http://dlang.org/version.html#StaticIfCondition) :( –

2

Jednym z rozwiązań jest częściowo specjalizacja szablonu klasy.

#include <iostream> 
#include <type_traits> 

template <typename T> class Test { }; 

template <typename Ret, typename... Args> 
class Test<Ret (Args...)> 
{ 
public: 
    Ret operator()(Args...) 
    { 

     std::cout<<"non-void function"<<std::endl; 
     Ret returnVal; 
     return returnVal; 

    } 
}; 

template <typename... Args> 
class Test<void (Args...)> 
{ 
public: 
    void operator()(Args...) 
    { 

     std::cout<<"void function"<<std::endl; 
    } 
}; 


int main(int argc, char * argv[]) 
{ 
    Test<void (char)> test; 
    test('k'); 
} 
Powiązane problemy