Yahoo
Yahoo

Reputation: 63

Default parameters for polymorphic lambda

I'm trying to write a macro that would shorten lambda expression syntax when single return statement is all it's needed. My first attempt looked like this:

struct void_t{};                                                                                                                                     

#define LR(EXPR) (auto&& p_1 = void_t{}, auto&& p_2 = void_t{}, auto&& p_3 = void_t{}){ return EXPR; }

Unfortunately it does not work as I would expect. Default arguments are basically ignored and I cannot call this lambda with less than three params:

std::cout << []LR(p_1)("test"); // compile error                                                                                                     
std::cout << []LR(p_1 + p_2)(2, 3); // compile error                                                                                                 
std::cout << []LR(p_1 + p_2)(std::string("hello "), std::string(" world!")); // compile error                                                        
std::cout << []LR(p_1 + p_2)(2, 3, 0); // OK                                                                          

I managed to devise some convoluted workaround that seemed to work but it has issues:

template<class LT> struct lambda_wrapper                                        
{                                                                               
    lambda_wrapper(LT p_lambda): m_lambda(p_lambda){}                           

    template<class T1, class T2, class T3>                                      
    auto operator()(T1&& p_1, T2&& p_2, T3&& p_3) const                         
    {                                                                           
        return m_lambda(std::forward<T1>(p_1),                                  
                        std::forward<T2>(p_2),                                  
                        std::forward<T3>(p_3));                                 
    }                                                                           

    template<class T1, class T2> auto operator()(T1&& p_1, T2&& p_2) const      
    {                                                                           
        return m_lambda(std::forward<T1>(p_1), std::forward<T2>(p_2), void_t{});
    }                                                                           

    template<class T1> auto operator()(T1&& p_1) const                          
    {                                                                           
        return m_lambda(std::forward<T1>(p_1), void_t{}, void_t{});             
    }                                                                           

    auto operator()() const                                                     
    {                                                                           
        return m_lambda(void_t{}, void_t{}, void_t{});                          
    }                                                                           
private:                                                                        
    LT m_lambda;                                                                
};                                                                              

template <class LT> lambda_wrapper<LT> operator++(LT&& p_lambda, int)           
{                                                                               
    return {std::forward<LT>(p_lambda)};                                        
}                                                                               

#define LR(EXPR) (auto&& p_1, auto&& p_2, auto&& p_3){ return EXPR; }++         

Anybody has a better idea?

Upvotes: 3

Views: 178

Answers (4)

Yahoo
Yahoo

Reputation: 63

Expanding upon your contributions I came up with following implementation:

namespace detail
{

struct void_t{};
template<int I> struct int_t{};

template<class TT> void_t get_param(void_t, const TT&)
{ 
    return void_t{};
}

template<int I, class TT> auto get_param(int_t<I>, const TT& p_tuple)
-> std::tuple_element_t<I, TT>&
{ 
    return std::get<I>(p_tuple);
}

}

#define LR(EXPR) (auto&&... ps) \
{\
    auto p = std::forward_as_tuple(ps...);\
    auto&& p_1 = get_param(std::conditional_t<sizeof...(ps) >= 1,\
                                              detail::int_t<0>,\
                                              detail::void_t>{}, p);\
    auto&& p_2 = get_param(std::conditional_t<sizeof...(ps) >= 2,\
                                              detail::int_t<1>,\
                                              detail::void_t>{}, p);\
    auto&& p_3 = get_param(std::conditional_t<sizeof...(ps) >= 3,\
                                              detail::int_t<2>,\
                                              detail::void_t>{}, p);\
    return EXPR;\
}

int main()
{
    std::cout << []LR(p_1)("test ", "test2");
    std::cout << []LR(p_1 + p_2)(std::string("Hallo "), std::string("World! "));
    int l_1 = 4;
    std::cout << [=]LR(p_1 + p_2 + p_3 + l_1)(1, 2, 3);
    std::cout << []LR(std::get<3>(p))(1, 2, 3, 4);    
}

Can anyone see any problem with this?

Upvotes: 0

Oktalist
Oktalist

Reputation: 14714

Building upon Daniel's answer, we can encode the tuple indexes as std::integral_constant types and write an extension of std::tuple with an operator[] template which can deduce the index from the type of its argument:

namespace detail {
    template <typename... Ts> struct tuple : std::tuple<Ts...> {
        using std::tuple<Ts...>::tuple;
        template <typename T> constexpr auto operator[](T)
        -> std::tuple_element_t<T::value, std::tuple<Ts...>>
        { return std::get<T::value>(*this); }
    };
    template <typename... Ts> constexpr tuple<Ts&&...> forward_as_tuple(Ts&&... vs) {
        return tuple<Ts&&...>{ std::forward<Ts>(vs)... };
    }
    namespace placeholders {
        constexpr std::integral_constant<size_t, 0> _0;
        constexpr std::integral_constant<size_t, 1> _1;
        constexpr std::integral_constant<size_t, 2> _2;
        //...
    }
}

#define LR(EXPR) (auto&&... _ps) \
{ using namespace detail::placeholders; auto p = detail::forward_as_tuple(_ps...); return EXPR; }

Which leads to

std::cout << []LR(p[_0])("test");
std::cout << []LR(p[_0] + p[_1])(2, 3); 
std::cout << []LR(p[_0] + p[_1])(std::string("hello "), std::string(" world!"));

Live example

Update: As Daniel alluded to in a comment, we can specialize our tuple type for each number of arguments, with named aliases to the tuple elements:

namespace detail {
    template <typename... Ts> struct tuple : std::tuple<Ts...> {
        using std::tuple<Ts...>::tuple;
    };
    template <typename T0> struct tuple<T0> : std::tuple<T0> {
        using std::tuple<T0>::tuple;
        T0&& _0 = std::get<0>(*this);
    };
    template <typename T0, typename T1> struct tuple<T0, T1> : std::tuple<T0, T1> {
        using std::tuple<T0, T1>::tuple;
        T0&& _0 = std::get<0>(*this);
        T1&& _1 = std::get<1>(*this);
    };
    template <typename T0, typename T1, typename T2> struct tuple<T0, T1, T2> : std::tuple<T0, T1, T2> {
        using std::tuple<T0, T1, T2>::tuple;
        T0&& _0 = std::get<0>(*this);
        T1&& _1 = std::get<1>(*this);
        T2&& _2 = std::get<2>(*this);
    };
    //...
    template <typename... Ts> constexpr tuple<Ts&&...> forward_as_tuple(Ts&&... vs) {
        return tuple<Ts&&...>{ std::forward<Ts>(vs)... };
    }
}

#define LR(EXPR) (auto&&... _ps) \
{ auto p = detail::forward_as_tuple(_ps...); return EXPR; }

Hence:

std::cout << []LR(p._0)("test");
std::cout << []LR(p._0 + p._1)(2, 3); 
std::cout << []LR(p._0 + p._1)(std::string("hello "), std::string(" world!"));

Live example

Upvotes: 4

yuri kilochek
yuri kilochek

Reputation: 13486

Boost.Preprocessor based solution:

#include <boost/preprocessor/tuple/to_seq.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/seq/cat.hpp>
#include <boost/preprocessor/seq/to_tuple.hpp>

#define _AUTO(_0, _1, name) \
    (auto&& name)

#define _BODY(...) \
    { return __VA_ARGS__; }

#define LR(...) \
    BOOST_PP_SEQ_TO_TUPLE(BOOST_PP_SEQ_FOR_EACH(_AUTO, _, BOOST_PP_TUPLE_TO_SEQ((__VA_ARGS__)))) _BODY

#include <iostream>

int main()
{
    std::cout << []LR(x)(x)("test");                                                                                                   
    std::cout << []LR(x, y)(x + y)(2, 3);                                                                                              
    std::cout << []LR(x, y)(x + y)(std::string("hello "), std::string(" world!"));                                                          
}

Upvotes: 2

Daniel Frey
Daniel Frey

Reputation: 56863

More generic, but with a less convenient syntax to access the elements from within the expression:

#define LR(EXPR) (auto&&... ps) \
{ auto p = std::forward_as_tuple(ps...); return EXPR; }

Which leads to

std::cout << []LR(std::get<0>(p))("test");
std::cout << []LR(std::get<0>(p) + std::get<1>(p))(2, 3); 
std::cout << []LR(std::get<0>(p) + std::get<1>(p))(std::string("hello "), std::string(" world!"));

Live example


Update: Building upon @Oktalist's answer (+1), one can also use the placeholders provided by the standard:

#include <iostream>
#include <tuple>
#include <functional>

namespace detail
{
template<typename T>
struct tuple_with_placeholder : T
{
    using T::T;

    template<typename PH>
    auto operator[](PH) const
    {
        return std::get<std::is_placeholder<PH>::value-1>(*this);
    }
};
}

#define LR(EXPR) (auto&&... ps) { \
    detail::tuple_with_placeholder<decltype(std::forward_as_tuple(ps...))> p(ps...); \
    using namespace std::placeholders; \
    return EXPR; \
}

int main()
{
    std::cout << []LR(p[_1])("test");
    std::cout << []LR(p[_1]+p[_2])(2, 3); 
    std::cout << []LR(p[_1]+p[_2])(std::string("hello "), std::string(" world!"));
}

Live example

Upvotes: 3

Related Questions