Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

[SOLVED] wyrazenia lambda + traits + static_assert

Ostatnio zmodyfikowano 2013-08-16 20:20
Autor Wiadomość
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:

C/C++
#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 { };
   
    //T is a function type
    constexpr static ReturnType( * func_ptr )( Args...) = nullptr;
   
    template < typename C >
    constexpr static bool test( check_obj < C, func_ptr >* ) { return true; }
   
   
    //T is a class type
    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 ); //OK.
    make_check( cA() ); //OK.
   
    // make_check(&B); //ERROR, ale tak ma być
    // make_check(cB()); //ERROR, ale tak ma być
   
    // make_check([](string,unsigned)->void{}); //ERROR, a nie powinno go być
   
    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ć?
P-90396
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.
P-90399
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...
P-90400
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:
C/C++
#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:
C/C++
#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 { } );
}
P-90401
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:
C/C++
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.
P-90403
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:
C/C++
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.
P-90404
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.
P-90405
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.
P-90406
« 1 » 2
  Strona 1 z 2 Następna strona