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

时间:2017-06-14 10:43:59

标签: c++ c++11 sfinae rvalue-reference rvalue

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

如果输入容器是r值,它应该就地工作,否则创建一个副本。这是通过调度到namespace internal中的相应版本来实现的。如果无法覆盖容器的value_type,则应禁用r值版本 - 例如std::pair<const int, int> - 即使容器是r值。

以下代码works as expected包含clang和当前版本的gcc(&gt; = 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
}

然而not compileMSVC++上的GCC 6.2确实this question,因为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++

请参阅Library Defect Report 2729different inserter的答案。

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

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

4 个答案:

答案 0 :(得分:2)

问题似乎是MSVC std::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 ) );
}

live exampleother live example

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

禁用operator=次禁用的其他类型也可能需要can_self_assign个专精。值得注意的示例可能包括tuple<Ts...>vector<T,A>以及类似的内容。

我不知道何时以及是否需要编译器operator=&#34;不存在&#34;如果它不适用于std类型;我记得std::vector在某一点上并不需要,但我也记得提出这些要求的提案。

答案 1 :(得分:0)

您没有说您使用的是哪种版本的Visual C ++编译器,但是您是否尝试过使用尾随返回语法?

换句话说,取而代之:

template <typename Pred, typename Container,
          typename Out = typename std::remove_reference<Container>::type>
Out drop_if( Pred pred, Container&& xs )

有这样的东西吗?

template <typename Pred, 
          typename Container>
auto drop_if(Pred pred, Container&& xs) -> std::remove_reference<Container>::type

Visual C ++编译器逐渐添加了C ++ 11/14/17功能。我一直在努力使模板MPL工作,并且不得不解决一些“应该”但不能工作的事情。

我会承认这是一个猜测,但你可以尝试一下。

答案 2 :(得分:0)

如果你希望你的代码专门用于像对象这样的元组的容器,你可以这样做(残酷但是在旧版本的gcc以及MSVC上工作):

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

namespace internal
{
    template <class... Ts>
        int foo(Ts&&... ts);

    template <typename Pred, typename Container, std::size_t... Is>
    auto drop_if( Pred pred, Container&& xs, std::index_sequence<Is...>) -> decltype(foo(std::declval<typename std::tuple_element<Is, typename std::remove_reference<typename Container::value_type>::type>::type&>() = std::declval<typename std::tuple_element<Is, typename std::remove_reference<typename Container::value_type>::type>::type>()...),  xs)
    {
        std::cout << "r-value" << std::endl;
        xs.erase( std::remove_if( std::begin( xs ), std::end( xs ), pred ), std::end( xs ) );
        return xs;
    }

    template <typename Pred, typename Container, class I>
    Container drop_if( Pred pred, const Container& xs, I )
    {
        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 internal::drop_if( pred, std::forward<decltype(xs)>( xs ), std::make_index_sequence<std::tuple_size<typename Out::value_type>::value>{} );
}

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
}

[live demo]

答案 3 :(得分:0)

很好的发现,在这些变化得到修复之前,我为这个角落案例提供了我的解决方案。

namespace internal {
template <typename T>
struct my_is_pair {
  static constexpr bool value = false;
};

template <typename K, typename V>
struct my_is_pair<std::pair<K, V>> {
  static constexpr bool value = true;
};

template <typename T, typename U, typename TIsPair = void>
struct my_is_assignable : std::is_assignable<T, U> {
  using is_pair_type = std::false_type;
};

template <typename T, typename U>
struct my_is_assignable<T,
                        U,
                        typename std::
                          enable_if<my_is_pair<typename std::decay<T>::type>::value
                                    && my_is_pair<typename std::decay<U>::type>::value>::
                            type>
  : std::integral_constant<bool,
                           std::is_assignable<
                             typename std::remove_reference<T>::type::first_type&,
                             const typename std::remove_reference<U>::type::first_type&>::
                               value
                             && std::is_assignable<
                                  typename std::remove_reference<T>::type::second_type&,
                                  const typename std::remove_reference<U>::type::
                                    second_type&>::value> {
  using is_pair_type = std::true_type;
};

template <
  typename Pred,
  typename Container,
  typename = typename std::
    enable_if<my_is_assignable<
                typename std::remove_reference<Container>::type::value_type,
                typename std::remove_reference<Container>::type::value_type>::value
              && std::is_rvalue_reference<Container&&>::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 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 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()); // r-value

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

这只是为the defect report中建议的对类型引入is_assignable专门化。我添加的另一件事是std::is_rvalue_reference,以防止来自对左值引用的调用(在您的解决方案中,它被Container::value_type中的失败替换禁用,当Container为{{1时失败}}