Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dispatching r-values and l-values differently and using sfinae to disable one option

I would like to implement a function drop_if. Given a unary predicate and a sequential container, it returns a container of the same type holding only the elements from the original one not fulfilling the predicate.

If the input container is an r-value it should work in-place otherwise create a copy. This is achieved by dispatching to the appropriate version in namespace internal. The r-value version should be disabled if the value_type of the container can not be overwritten - like std::pair<const int, int> for example - even if the container is an r-value.

The following code works as expected with clang and current versions of gcc (>=6.3).

#include <algorithm>
#include <iostream>
#include <iterator>
#include <type_traits>
#include <utility>
#include <vector>

namespace internal
{
    template <typename Pred, typename Container,
        typename = typename std::enable_if<
        std::is_assignable<
            typename Container::value_type&,
            typename Container::value_type>::value>::type>
    Container drop_if( Pred pred, Container&& xs )
    {
        std::cout << "r-value" << std::endl;
        xs.erase( std::remove_if( std::begin( xs ), std::end( xs ), pred ), std::end( xs ) );
        return std::move( xs );
    }

    template <typename Pred, typename Container>
    Container drop_if( Pred pred, const Container& xs )
    {
        std::cout << "l-value" << std::endl;
        Container result;
        auto it = std::back_inserter( result );
        std::remove_copy_if( std::begin( xs ), std::end( xs ), it, pred );
        return result;
    }
} // namespace internal

template <typename Pred, typename Container,
    typename Out = typename std::remove_reference<Container>::type>
    Out drop_if( Pred pred, Container&& xs )
{
    return std::move( internal::drop_if( pred, std::forward<decltype(xs)>( xs ) ) );
}

typedef std::pair<int, int> pair_t;
typedef std::vector<pair_t> vec_t;

bool sum_is_even( pair_t p )
{
    return (p.first + p.second) % 2 == 0;
}

typedef std::pair<const int, int> pair_c_t;
typedef std::vector<pair_c_t> vec_c_t;

bool sum_is_even_c( pair_c_t p)
{
    return (p.first + p.second) % 2 == 0;
}

int main()
{
    vec_c_t v_c;
    drop_if( sum_is_even_c, v_c ); // l-value
    drop_if( sum_is_even_c, vec_c_t() ); // l-value

    vec_t v;
    drop_if( sum_is_even, v ); // l-value
    drop_if( sum_is_even, vec_t() ); // r-value
}

However it does not compile on MSVC++ and GCC 6.2, because they behave incorrectly for std::is_assignable:

using T = std::pair<const int, int>;
const auto ok = std::is_assignable<T&, T>::value;
// ok == true on GCC 6.2 and MSVC++

See answer to this question and Library Defect Report 2729.

I would like it to work with different containers and with different kinds of objects, e.g. std::vector<double>, std::map<int, std::string> etc. The std::map case (using a different inserter) is the situation in which I encountered the problem with value_types of std::pair<const T, U>.

Do you have any ideas how the dispatch / sfinae could be changed to also work for MSVC++ (ver. MSVC++ 2017 15.2 26430.6 in my case) and for GCC 6.2 downwards?

like image 903
Tobias Hermann Avatar asked Jun 14 '17 10:06

Tobias Hermann


1 Answers

The problem appears to be that MSVC's std::pair<const T, U>::operator= is not SFINAE disabled. It exists even if instantiating it does not work.

So when you detect if it exists, it exists. If you execute it, it fails to compile.

We can work around this. But it is a workaround.

namespace internal
{
    template <typename Pred, typename Container>
    Container drop_if( std::true_type reuse_container, Pred pred, Container&& xs )
    {
        std::cout << "r-value" << std::endl;
        xs.erase( std::remove_if( std::begin( xs ), std::end( xs ), pred ), std::end( xs ) );
        return std::forward<Container>( xs );
    }

    template <typename Pred, typename Container>
    Container drop_if( std::false_type reuse_container, Pred pred, const Container& xs )
    {
        std::cout << "l-value" << std::endl;
        Container result;
        auto it = std::back_inserter( result );
        std::remove_copy_if( std::begin( xs ), std::end( xs ), it, pred );
        return result;
    }
} // namespace internal

template<bool b>
using bool_k = std::integral_constant<bool, b>;

template<class T>
struct can_self_assign {
    using type = std::is_assignable<T&, T>;
};

template<class T>
using can_self_assign_t = typename can_self_assign<T>::type;

template<class T0, class T1>
struct can_self_assign<std::pair<T0, T1>>
{
    enum { t0 = can_self_assign_t<T0>::value, t1 = can_self_assign_t<T1>::value, x = t0&&t1 };
    using type = bool_k< x >;
};

template<>
struct can_self_assign<std::tuple<>>
{
    using type = bool_k< true >;
};
template<class T0, class...Ts>
struct can_self_assign<std::tuple<T0, Ts...>>
{
    using type = bool_k< can_self_assign_t<T0>::value && can_self_assign_t<std::tuple<Ts...>>::value >;
};


template <typename Pred, typename Container,
    typename Out = typename std::remove_reference<Container>::type>
    Out drop_if( Pred pred, Container&& xs )
{
    using dContainer = typename std::decay<Container>::type;
    using can_assign = can_self_assign_t<typename dContainer::value_type>;
    using cannot_reuse = std::is_lvalue_reference<Container>;

    using reuse = std::integral_constant<bool, can_assign::value && !cannot_reuse::value >;

    return internal::drop_if( reuse{}, pred, std::forward<Container>( xs ) );
}

live example and other live example.

I also changed your SFINAE dispatching to tag based dispatching.

Other types with defective operator= disabling may also need can_self_assign specializations. Notable examples may include tuple<Ts...> and vector<T,A> and similar.

I don't know when and if compilers where required to have operator= "not exist" if it wouldn't work in std types; I remember it was not required at one point for std::vector, but I also remember a proposal adding such requirements.

like image 116
Yakk - Adam Nevraumont Avatar answered Oct 13 '22 18:10

Yakk - Adam Nevraumont