Pull to refresh
14
0
Send message
С помощью небольшой магии можно сделать лямбды:
template <class T>
struct Eval {
    template <class...>
    struct call {
        using value = T;
    };
};

template <class... T>
struct Apply {
    template <class...>
    struct call;
};

template <class F, class... X>
struct Eval<Apply<F, X...>> {
    template <class...>
    struct call {
        using value = apply<apply<Eval<F>>, apply<Eval<X>>...>;
    };
};

template <class... T>
template <class...>
struct Apply<T...>::call {
    using value = apply<Eval<Apply<T...>>>;
};

template <class F, class X>
struct Substitute {
    template <class...>
    struct call {
        using value = F;
    };
};

template <class X>
struct Substitute<X, X> {
    template <class Y>
    struct call {
        using value = Y;
    };
};

template <template <class...> class F, class... T, class X>
struct Substitute<F<T...>, X> {
    template <class Y>
    struct call {
        using value = F<apply<Substitute<T, X>, Y>...>;
    };
};

template <class X, class F>
struct Lambda {
    template <class Y>
    struct call {
        using value = apply<Eval<apply<Substitute<F, X>, Y>>>;
    };
};

template <class X, class F>
struct Eval<Lambda<X, F>> {
    template <class...>
    struct call {
        using value = Lambda<X, F>;
    };
};

Которые можно применять на месте:

struct X{};
using lambda = Lambda<X, Apply<List, X>>;
using result = apply<lambda, One>; // Cons<One, Nil>


Польный пример
Для себя нашел, что использовать template template для передачи метафункций не удобно, как минимум возникают проблемы с возвратом функций из функций. Вместо этого я в явном виде объявляю метафункцию аппликации:
template <class F, class... X>
using apply = typename F::template call<X...>::value;

А далее все функции имеют вложенный шаблон call:
struct And {
    template <class X, class... Y>
    struct call;
};

template <class X>
struct And::call<X> {
    using value = X;
};

template <class X, class... Y>
struct And::call<Zero, X, Y...> {
    using value = Zero;
};

template <class X, class... Y>
struct And::call<One, X, Y...> {
    using value = apply<And, X, Y...>;
};

Таким образом сами функции более не являются шаблоном от параметров, и ими можно оперировать как другими объектами — положить в список, вернуть в виде результата другой функции и т.п.

Код
#include <iostream>

template <class F, class... X>
using apply = typename F::template call<X...>::value;

template <class T>
struct Identity {
    template <class...>
    struct call {
        using value = T;
    };
};

struct One : Identity<One> {
};

struct Zero : Identity<Zero> {
};

struct Nil : Identity<Nil> {
};

struct Not {
    template <class X>
    struct call;
};

template <>
struct Not::call<Zero> {
    using value = One;
};

template <>
struct Not::call<One> {
    using value = Zero;
};

struct And {
    template <class X, class... Y>
    struct call;
};

template <class X>
struct And::call<X> {
    using value = X;
};

template <class X, class... Y>
struct And::call<Zero, X, Y...> {
    using value = Zero;
};

template <class X, class... Y>
struct And::call<One, X, Y...> {
    using value = apply<And, X, Y...>;
};


template <class H, class T>
struct Cons : Identity<Cons<H, T>> {
};

struct Head {
    template <class>
    struct call;
};

template <class H, class T>
struct Head::call<Cons<H, T>> {
    using value = H;
};

struct Tail {
    template <class>
    struct call;
};

template <class H, class T>
struct Tail::call<Cons<H, T>> {
    using value = T;
};

struct List {
    template <class...>
    struct call;
};

template <>
struct List::call<> {
    using value = Nil;
};

template <class H, class... T>
struct List::call<H, T...> {
    using value = Cons<H, apply<List, T...>>;
};

struct Map {
    template <class T, class F>
    struct call;
};

template <class H, class T, class F>
struct Map::call<Cons<H, T>, F> {
    using value = Cons<apply<F, H>, apply<Map, T, F>>;
};

template <class F>
struct Map::call<Nil, F> {
    using value = Nil;
};


std::ostream& operator<<( std::ostream& os, One )
{
    os << 1;
    return os;
}

std::ostream& operator<<( std::ostream& os, Zero )
{
    os << 0;
    return os;
}

std::ostream& operator<<( std::ostream& os, Nil )
{
    return os;
}

std::ostream& operator<<( std::ostream& os, And )
{
    os << "&&";
    return os;
}

template <class H, class T>
std::ostream& operator<<( std::ostream& os, Cons<H, T> )
{
    os << "{ " << H() << " " << T() << "}";
    return os;
}

int main()
{
    std::cout << One() << std::endl;
    // 1

    std::cout << apply<And, One, One, One, One>() << std::endl;
    // 1

    std::cout << apply<And, One, One, Zero, One>() << std::endl;
    // 0

    using list = apply<List, One, One, Zero, One>;

    std::cout << list() << std::endl;
    // { 1 { 1 { 0 { 1 }}}}

    std::cout << apply<Map, list, Not>() << std::endl;
    //{ 0 { 0 { 1 { 0 }}}}

    std::cout << apply<Map, list, One>() << std::endl;
    //{ 1 { 1 { 1 { 1 }}}}

    std::cout << apply<Map, list, Zero>() << std::endl;
    //{ 0 { 0 { 0 { 0 }}}}


    using list2 = apply<List, list, list>;

    std::cout << apply<Map, list2, Tail>() << std::endl;
    //{ { 1 { 0 { 1 }}} { { 1 { 0 { 1 }}} }}


    std::cout << apply<Map, list2, One>() << std::endl;
    //{ 1 { 1 }}

    std::cout << apply<List, One, And, Zero>() << std::endl;
    //{ 1 { && { 0 }}}
}

На текущем Debian для apt не работает bash-completion из коробки, так что «сокращиние» выходит весьма сомнительным.
Кстати, весьма полезная «оптимизация»: иногда возникает задача создать библиотеку-заглушку для линковки
#define for(...)
#define P(...)
int main() {
    for(;P("\n"),R--;P("|"))for(e=C;e--;P("_"+(*u++/8)%2))P("| "+(*u/4)%2);
}
Но это всё выглядит страшновато и плохо пахнет. Можем ли мы лучше?

Возможно, в рамках C++14 и можем, но я не придумал, как, а скорее нашёл доказательство, что этого сделать нельзя, но поля этой статьи слишком узки для него.


Можем, если вынести враппер на уровень выше:
call_c_func( doWithCallback, foo ).with_callback<&Foo::doFoo>();

Код
#include <tuple>

namespace detail {

template <class T, class = std::tuple<>>
struct pop_back;

template <class... Result>
struct pop_back<std::tuple<void*>, std::tuple<Result...>> {
    using type = std::tuple<Result...>;
};

template <class First, class Second, class... Tail, class... Result>
struct pop_back<std::tuple<First, Second, Tail...>, std::tuple<Result...>>
  : pop_back<std::tuple<Second, Tail...>, std::tuple<Result..., First>> {
};

template <class F>
struct c_func_info;

template <class R, class... Args>
struct c_func_info<R ( * )( Args... )> {
    using result_type = R;
    using args        = std::tuple<Args...>;
};

template <class R, class... Args>
struct c_func_info<R ( & )( Args... )> : c_func_info<R ( * )( Args... )> {
};

} // namespace detail

template <class, class, class, class>
struct Wrapper;

template <class F, class C, class R, class... Args>
struct Wrapper<F, C, R, std::tuple<Args...>> {

    template <R ( C::*Ptr )( Args... ), class... Args2>
    R with_callback( Args2... args2 )
    {
        return f( args2..., []( Args... args, void* userdata ) { return ( static_cast<C*>( userdata )->*Ptr )( args... ); }, c );
    }

    explicit Wrapper( F f_, C* c_ )
      : f( f_ )
      , c( c_ )
    {
    }

    F f;
    C* c;
};

template <class F, class C>
auto call_c_func( F f, C& c )
{
    using func_info     = detail::c_func_info<F>;
    using callback_info = detail::c_func_info<typename std::tuple_element<std::tuple_size<typename func_info::args>::value - 2,
                                                                          typename detail::pop_back<typename func_info::args>::type>::type>;
    using callback_args = typename detail::pop_back<typename callback_info::args>::type;
    return Wrapper<F, C, typename callback_info::result_type, callback_args>{f, &c};
}

//

#include <iostream>

int doWithCallback( int value, int ( *fn )( int, void* ), void* userdata )
{
    return fn( value, userdata ) * 3;
}

struct Foo {
    explicit Foo( int i )
      : m_i( i )
    {
    }

    int m_i;

    int doFoo( int val )
    {
        std::cout << m_i << " vs " << val << std::endl;
        return m_i * 2;
    }
};


int main()
{
    Foo foo{42};
    auto v = call_c_func( doWithCallback, foo ).with_callback<&Foo::doFoo>( 10 );
    std::cout << v << std::endl;
}

Мне тоже, но я еще не придумал как его можно использовать.
Забавно, что схожее предложение на днях было опубликовано в рамках WG21.

С другой стороны данная статья может быть использована как наглядный пример для дальнейшего устранения недостатков лямбд (variadic move-capture, const mutable lambda call, rvalue lambda и т.д.)
Например:
struct functor {
    void operator()( int ) const
    {
        std::cout << "int\n";
    }

    void operator()( std::reference_wrapper<int> ) const
    {
        std::cout << "std::reference_wrapper<int>\n";
    }
};

int k = 10;
part( functor(), k )();
part( functor(), std::ref( k ) )();
Не проще ли сделать так?
template <class F, class... Args>
auto part( F f, Args... args )
{
    return [=]( auto&&... args2 ) {
        return f( args..., std::forward<decltype( args2 )>( args2 )... );
    };
}
Впрочем, можно доработать методом из соседнего комментария
Заголовок спойлера
#include <iostream>
#include <type_traits>
#include <vector>

template <class T>
struct id {
    static id id_;
};

template <class T>
id<T> id<T>::id_;

template <class T>
struct visitor_base {
    using common_type = T;
    virtual void visit_base( T& obj, void* ptr ) = 0;
};

template <class T, class Base = void>
struct visitable : Base {
    void accept( typename Base::visitor_type& obj ) override
    {
        obj.visit_base( static_cast<T&>( *this ), &id<T>::id_ );
    }
};

template <class T>
struct visitable<T, void> {
    using visitor_type = visitor_base<T>;

    virtual void accept( visitor_type& obj )
    {
        obj.visit_base( static_cast<T&>( *this ), &id<T>::id_ );
    }
};

template <class...>
struct tag {
};

template <class T>
struct visitor_impl {
    virtual void visit( T& ) = 0;
};

template <class... Ts>
struct visitor : visitor_impl<Ts>..., std::common_type<Ts...>::type::visitor_type {
    using common_type = typename std::common_type<Ts...>::type::visitor_type::common_type;
    void visit_base( common_type& obj, void* ptr ) override
    {
        visit_( obj, ptr, tag<Ts...>() );
    }

private:
    void visit_( common_type& obj, void*, tag<> )
    {
    }

    template <class Head, class... Tail>
    void visit_( common_type& obj, void* ptr, tag<Head, Tail...> )
    {
        if ( ptr == &id<Head>::id_ ) {
            static_cast<visitor_impl<Head>&>( *this ).visit( static_cast<Head&>( obj ) );
        }
        else {
            visit_( obj, ptr, tag<Tail...>() );
        }
    }
};


struct entity : visitable<entity> {
};

struct geometry : visitable<geometry, entity> {
};

struct model : visitable<model, entity> {
};


struct test_visitor : visitor<entity, geometry, model> {
    void visit( entity& ) override
    {
        std::cout << "entity\n";
    }

    void visit( geometry& ) override
    {
        std::cout << "geometry\n";
    }

    void visit( model& ) override
    {
        std::cout << "model\n";
    }
};

int main()
{
    entity e;
    geometry g;
    model m;

    test_visitor v;

    e.accept( v );
    g.accept( v );
    m.accept( v );

    std::cout << "\n";

    std::vector<entity*> vec{&e, &g, &m};
    for ( auto it : vec ) {
        it->accept( v );
    }
}

Да, без RTTI тогда никак.
Вот, теперь должно быть правильно:
Заголовок спойлера
#include <iostream>
#include <vector>

template <class T>
struct visitor_base {
    template <class R>
    void visit_base( R& obj )
    {
        static_cast<typename R::visitor_type&>( *this ).visit( obj );
    }
};

template <class T>
struct visitor : T::parent_visitor_type {
    virtual void visit( T& ) = 0;
};


template <class T, class Base = void>
struct visitable : Base {
    using parent_visitor_type = typename Base::visitor_type;
    using visitor_type        = visitor<T>;

    void accept( typename Base::visitor_base_type& obj ) override
    {
        obj.visit_base( static_cast<T&>( *this ) );
    }
};

template <class T>
struct visitable<T, void> {
    using parent_visitor_type = visitor_base<T>;
    using visitor_base_type   = visitor<T>;
    using visitor_type        = visitor_base_type;

    virtual void accept( visitor_base_type& obj )
    {
        obj.visit( static_cast<T&>( *this ) );
    }
};

struct entity : visitable<entity> {
};

struct geometry : visitable<geometry, entity> {
};

struct geometry2 : geometry {
};

struct model : visitable<model, geometry2> {
};

struct test_visitor : visitor<model> {
    void visit( entity& obj ) override
    {
        std::cout << "entity\n";
    }

    void visit( geometry& obj ) override
    {
        std::cout << "geometry\n";
    }

    void visit( model& obj ) override
    {
        std::cout << "model\n";
    }
};

int main()
{
    entity e;
    geometry g;
    geometry2 g2;
    model m;

    test_visitor v;

    e.accept( v );
    g.accept( v );
    g2.accept( v );
    m.accept( v );

    std::vector<entity*> vec{&e, &g, &g2, &m};
    for ( auto it : vec ) {
        it->accept( v );
    }
}

Хотя вру, через ссылки на базовые классы работать не будет.
Я это вижу как-то так:
Код
#include <iostream>

template <class T>
struct visitor {
    virtual void visit( T& ) = 0;
};

template <class T, class... Base>
struct visitable : Base... {
    template <class Visitor>
    void accept( Visitor& obj )
    {
        static_cast<visitor<T>&>( obj ).visit( static_cast<T&>( *this ) );
    }
};

struct entity : visitable<entity> {
};

struct geometry : visitable<geometry, entity> {
};

struct model : visitable<model, geometry> {
};

struct test_visitor : visitor<entity>, visitor<geometry>, visitor<model> {
    void visit( entity& obj ) override
    {
        std::cout << "entity\n";
    }

    void visit( geometry& obj ) override
    {
        std::cout << "geometry\n";
    }

    void visit( model& obj ) override
    {
        std::cout << "model\n";
    }
};

int main()
{
    entity e;
    geometry g;
    model m;

    test_visitor v;

    e.accept( v );
    g.accept( v );
    m.accept( v );
}

При наличии режима «капитан очевидность» и root-прав не обязательно полагаться, что пользователь вот так вот просто оставил свои пароли лежать в открытом виде. Можно устроить MITM и выловить эти пароли прямо из трафика, а для случая TLS еще заменить системные сертификаты.
Для GCC вы можете делать как-то так:
#include <iostream>

#define PROGMEM

template <char... String>
struct ProgmemString {
    static const char PROGMEM v[sizeof...( String ) + 1];
};

template <char... String>
const char ProgmemString<String...>::v[sizeof...( String ) + 1] = {String..., '\0'};

template <typename CharT, CharT... String>
constexpr auto operator"" _progmem()
{
    return ProgmemString<String...>::v;
}

int main()
{
    std::cout << "progmem string"_progmem << std::endl;
}
Минусы: больший объём памяти, необходимый для выполнения.

А теперь я предлагаю вам подтвердить своё утверждение результатами тестов, где ваш сравниваемый код на javascript требует для выполнения меньше памяти.
Оператор простого присвоения нельзя определить вне класса. Впрочем для остальных операторов правило не меняется, при объявлении вне класса стоит возвращать референс на первый аргумент, а не значение.
Корректные операторы присвоения должны возвращать референс на *this, а не значение. Это де-факто стандартное ожидаемое поведение.

Information

Rating
Does not participate
Location
Москва и Московская обл., Россия
Date of birth
Registered
Activity