b00rt00s Temat założony przez niniejszego użytkownika |
[SOLVED] wyrazenia lambda + traits + static_assert » 2013-08-16 16:41:51 Napisałem szablonową funkcję, która wymaga podania jako argument funkcji o sygnaturze void (*)(string,unsigned) lub obiektu z operatorem operator()(string, unsigned). Oczywiście możliwe jest też podanie odpowiedniego wyrażenia lambda. Aby sprawdzić podczas kompilacji czy podany argument jest poprawny napisałem taki kod: #include <iostream>
using namespace std;
void A( string, unsigned ) { } void B( string, float ) { }
struct cA { void operator ()( string, unsigned ) { } };
struct cB { void operator ()( string, int ) { } };
template < typename T, typename ReturnType, typename...Args > struct _check_args_ { template < typename U, U > class check_obj { }; constexpr static ReturnType( * func_ptr )( Args...) = nullptr; template < typename C > constexpr static bool test( check_obj < C, func_ptr >* ) { return true; } template < typename C > constexpr static bool test( check_obj < ReturnType( C::* )( Args...), & C::operator () >* ) { return true; } template < typename > constexpr static bool test(...) { return false; } static const bool value = test < T >( nullptr ); };
template < typename T > void make_check( T ) { static_assert( _check_args_ < T, void, string, unsigned >::value, "Error message" ); }
int main() { make_check( & A ); make_check( cA() ); return 0; } Program działa wyśmienicie dla funkcji i dla obiektów funkcyjnych. Ale jak zmusić go do odpowiedniego interpretowania wyrażeń lambda? Ma ktoś pomysł jak to zrobić? |
|
Elaine |
» 2013-08-16 17:23:09 Domyślnie operator() lambd jest const, wyjątkiem są sytuacje, gdy lambda jest oznaczona jako mutable (5.1.2/5). Jeśli dodasz odpowiedni overload, to zacznie działać – nie tylko dla lambd, ale także dla normalnych klas, których operator() jest const. |
|
b00rt00s Temat założony przez niniejszego użytkownika |
» 2013-08-16 17:31:08 Dzięki wielkie Alueril! W życiu bym na to nie wpadł. Szukałem dwa dni po całym internecie różnych informacji, sam kombinowałem jak koń pod górę, a rozwiązanie było takie proste. Proste, jak się coś wie... |
|
Elaine |
» 2013-08-16 18:15:49 Można to jeszcze uprościć, check_obj nie jest tu potrzebny, wystarczy odpowiednio wykorzystane std::enable_if i std::is_same. Po dodaniu brakujących overloadów dla volatile i ref-qualifiers oraz zmianie składni wywołania na bardziej przypominającą std::function, wygląda to tak: #include <type_traits>
template < typename T, typename U > struct check_args;
template < typename T, typename ReturnType, typename...Args > struct check_args < T, ReturnType( Args...) > { template < typename U, typename V > using true_type_if_same = typename std::enable_if < std::is_same < U, V >::value, std::true_type >::type; template < typename C > static auto test( int )->true_type_if_same < C, ReturnType( * )( Args...) >; template < typename C > static auto test( int )->true_type_if_same < decltype( & C::operator () ), ReturnType( C::* )( Args...) >; template < typename C > static auto test( int )->true_type_if_same < decltype( & C::operator () ), ReturnType( C::* )( Args...) &>; template < typename C > static auto test( int )->true_type_if_same < decltype( & C::operator () ), ReturnType( C::* )( Args...) &&>; template < typename C > static auto test( int )->true_type_if_same < decltype( & C::operator () ), ReturnType( C::* )( Args...) const >; template < typename C > static auto test( int )->true_type_if_same < decltype( & C::operator () ), ReturnType( C::* )( Args...) const &>; template < typename C > static auto test( int )->true_type_if_same < decltype( & C::operator () ), ReturnType( C::* )( Args...) const &&>; template < typename C > static auto test( int )->true_type_if_same < decltype( & C::operator () ), ReturnType( C::* )( Args...) volatile >; template < typename C > static auto test( int )->true_type_if_same < decltype( & C::operator () ), ReturnType( C::* )( Args...) volatile &>; template < typename C > static auto test( int )->true_type_if_same < decltype( & C::operator () ), ReturnType( C::* )( Args...) volatile &&>; template < typename C > static auto test( int )->true_type_if_same < decltype( & C::operator () ), ReturnType( C::* )( Args...) volatile const >; template < typename C > static auto test( int )->true_type_if_same < decltype( & C::operator () ), ReturnType( C::* )( Args...) volatile const &>; template < typename C > static auto test( int )->true_type_if_same < decltype( & C::operator () ), ReturnType( C::* )( Args...) volatile const &&>; template < typename > static auto test(...)->std::false_type; using type = decltype( test < typename std::decay < T >::type >( 0 ) ); static constexpr bool value = type(); };
template < typename T, typename ReturnType, typename...Args > constexpr bool check_args < T, ReturnType( Args...) >::value; Mały przykład użycia: #include <iostream>
template < typename T > void check( T && ) { std::cout << check_args < T, void( double, int ) >::value << '\n'; }
void f1( double, int ) { } void f2( float, int ) { } void f3( double, unsigned ) { } void f4( float, unsigned ) { } int f5( double, int ) { return 0; }
struct Foo { void operator ()( double, int ) { } };
struct Bar { void operator ()( double, int ) const volatile && { } };
int main() { check( & f1 ); check( & f2 ); check( & f3 ); check( & f4 ); check( & f5 ); check( Foo() ); check( Bar() ); check([]( int ) { } ); check([]( double, int ) { } ); check([]( double, int ) mutable { } ); } |
|
b00rt00s Temat założony przez niniejszego użytkownika |
» 2013-08-16 19:24:14 Bardzo ciekawy przykład, ale... nie skompilował mi się :P. Dostaję komunikat mówiący, że nie można przeciążyć funkcji test. Kompiluje oczywiście z flagą -std=c++11 (GCC 4.8.1). Nie rozumiem w tym kodzie tylko jednego: do czego potrzebny jest końcowy fragment szablonu: template < typename T, typename ReturnType, typename...Args > constexpr bool check_args < T, ReturnType( Args...) >::value; Poza tym chyba czas przyglądnąć się nowościom w STL. Te kilka funkcji jest bardzo przydatnych i bardzo poprawia czytelność kodu. |
|
Elaine |
» 2013-08-16 19:45:37 Bardzo ciekawy przykład, ale... Nie skompilował mi się :P. Dostaję komunikat mówiący, że nie można przeciążyć funkcji test. Kompiluje oczywiście z flagą -std=c++11 (GCC 4.8.1). |
GCC 4.8.1 ma bug, przez który nie można przeciążyć funkcji ani specjalizować szablonu, jeśli jedyną różnicą są ref-qualifiers w typie parametru. Ja mam 4.8.2 sprzed tygodnia, u mnie działa, Clang też nie ma z tym żadnych problemów. Jako obejście możesz usunąć wszystkie te wersje test, które korzystają z ref-qualifiers (z dwunastu wersji zrobią się tylko cztery) lub schować je pod np. #ifdef GCC_HAS_BUGGY_REF_QUALIFIER_SPECIALIZATIONS. Nie rozumiem w tym kodzie tylko jednego: do czego potrzebny jest końcowy fragment szablonu:
template < typename T, typename ReturnType, typename...Args > constexpr bool check_args < T, ReturnType( Args...) >::value; |
Jeśli w jakimś miejscu kodu zostanie pobrany adres zmiennej value (lub nastąpi inny odr-use tej zmiennej), to konieczna jest jej definicja, w przeciwnym wypadku zachowanie będzie niezdefiniowane (w tym wypadku prawdopodobnie objawi się błędami linkera). 9.4.2/3: The member shall still be defined in a namespace scope if it is odr-used (3.2) in the program and the namespace scope definition shall not contain an initializer. |
Ten fragment kodu właśnie dostarcza definicję tej zmiennej. |
|
b00rt00s Temat założony przez niniejszego użytkownika |
» 2013-08-16 19:55:59 Jeszcze raz dzięki. Będę musiał poczekać aż w repo Arch'a pojawi się nowe GCC i będzie git. Pozdrawiam. |
|
Elaine |
» 2013-08-16 20:02:58 Jeśli używasz Archa, to nie musisz czekać – możesz zbudować nowszy snapshot (GCC 4.8.1 w Archu to tak naprawdę 4.8.2! Niestety, o trzy tygodnie za stare, żeby ten bug był poprawiony) korzystając z ABS. Wystarczy zmienić _snapshot=4.8-20130725 w PKGBUILD na _snapshot=4.8-20130815, albo nawet _snapshot=LATEST-4.8. |
|
« 1 » 2 |