以不同方式调度r值和l值并使用sfinae禁用一个选项

Tob*_*ann 8 c++ rvalue sfinae rvalue-reference c++11

我想实现一个功能drop_if.给定一元谓词和顺序容器,它返回一个相同类型的容器,只容纳原始元素中不满足谓词的元素.

如果输入容器是r值,它应该就地工作,否则创建一个副本.这是通过调度到适当的版本来实现的namespace internal.如果value_type容器的容量不能被覆盖(std::pair<const int, int>例如,即使容器是r值),也应该禁用r值版本.

以下代码与clang和当前版本的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
}
Run Code Online (Sandbox Code Playgroud)

但是它不能MSVC++GCC 6.2编译,因为它们的行为不正确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++
Run Code Online (Sandbox Code Playgroud)

请参阅此问题的答案和图书馆缺陷报告2729.

我希望它可以使用不同的容器和不同类型的对象,例如std::vector<double>,std::map<int, std::string>等等.std::map案例(使用不同的插入器)是我遇到问题的value_types情况std::pair<const T, U>.

您是否有任何想法如何更改dispatch/sfinae以适用于MSVC++(在我的情况下为版本MSVC++ 2017 15.2 26430.6)和GCC 6.2向下?

Yak*_*ont 2

问题似乎是 MSVCstd::pair<const T, U>::operator=未禁用 SFINAE。即使实例化它不起作用,它仍然存在。

所以当你检测到它是否存在时,它就存在。如果执行它,则无法编译。

我们可以解决这个问题。但这是一个解决方法。

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 ) );
}
Run Code Online (Sandbox Code Playgroud)

活生生的例子其他活生生的例子

我还将您的 SFINAE 调度更改为基于标签的调度。

其他具有缺陷operator=禁用的类型可能也需要can_self_assign专门化。值得注意的例子包括tuple<Ts...>vector<T,A>以及类似的。

我不知道编译器何时以及是否需要operator=“不存在”,如果它不能在std类型中工作;我记得曾经没有要求过std::vector,但我也记得有一项提案添加了此类要求。