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?
internal::drop_if
takes eithertrue_type
orfalse_type
as its first argument.::drop_if
passesstd::is_assignable<...>{}
tointernal::drop_if
. MSVC's SFINAE support is flakey, and you don't need SFINAE here, just dispatching. Then we can attackis_assignable
problem directly. (decltype in SFINAE makes MSVC die; decltype in tag dispatching does not) – Stubbsreturn xs;
-- you need to move or forward here. – Stubbsreturn xs;
? Isn't this happening automatically? Edit: Oh, I now understand. It is not superfluous in that case. Thank you. I edited my question accordingly. Also, sorry I do not understand your other comment. Could you please show what you mean? – Outlawry::drop_if
, then tag-dispatch to the correctinternal::drop_if
. This eliminates SFINAE, which MSVC can have problems with. – Stubbsstd::is_assignable
on MSVC and on older GCCs? – Outlawrydrop_if
not work withstd::map
? – Outlawryauto itOut = internal::get_back_inserter<ContainerOut>(ys);
in my real code andinternal::get_back_inserter
is overloaded to different containers, e.g.: gist.github.com/Dobiasd/2f0d2472e5f30e44442a8232a171be4b – Outlawry