以不同方式分派右值和左值并使用 sfinae 禁用一个选项
Dispatching r-values and l-values differently and using sfinae to disable one option
我想实现一个功能drop_if
。给定一个一元谓词和一个顺序容器,它 returns 一个相同类型的容器,仅包含原始元素中不满足谓词的元素。
如果输入容器是 r 值,它应该就地工作,否则创建一个副本。这是通过分派到 namespace internal
中的适当版本来实现的。如果容器的 value_type
不能被覆盖,则应禁用右值版本 - 例如 std::pair<const int, int>
- 即使容器是右值。
以下代码 works as expected 带有 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
}
但是 not compile on MSVC++ and 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++
查看 and Library Defect Report 2729 的答案。
我希望它适用于不同的容器和不同种类的对象,例如std::vector<double>
、std::map<int, std::string>
等。std::map
案例(使用different inserter)是我遇到value_types
of std::pair<const T, U>
问题的情况.
您是否知道如何更改 dispatch / sfinae 以使其也适用于 MSVC++(ver.MSVC++ 2017 15.2 26430.6 在我的例子中)和向下的 GCC 6.2?
您没有说明您使用的是哪个版本的 Visual C++ 编译器,但您是否尝试过使用尾随 return 语法?
换句话说,替换为:
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 工作,并且不得不解决一些 "should" 工作但不工作的事情。
我承认这有点猜测,但您可以试一试。
如果您希望您的代码专门用于类似对象的元组容器,您可以这样做(残酷但适用于旧版本的 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
}
问题似乎是 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 example and other live example.
我还将您的 SFINAE 调度更改为基于标签的调度。
其他 operator=
禁用有缺陷的类型可能也需要 can_self_assign
专业化。值得注意的示例可能包括 tuple<Ts...>
和 vector<T,A>
以及类似的。
我不知道编译器何时以及是否需要 operator=
"not exist" 如果它在 std
类型中不起作用;我记得 std::vector
曾经没有要求,但我还记得有一项提案添加了此类要求。
很好的发现,在这些更改得到修复之前,我会提供针对这个极端情况的解决方案。
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
}
这只是引入了 is_assignable
对类型的特化,正如 the defect report 中所建议的那样。我添加的另一件事是 std::is_rvalue_reference
以防止对左值引用的调用(在你的解决方案中它被 Container::value_type
中的失败替换禁用,当 Container
是 vector<...>&
.
我想实现一个功能drop_if
。给定一个一元谓词和一个顺序容器,它 returns 一个相同类型的容器,仅包含原始元素中不满足谓词的元素。
如果输入容器是 r 值,它应该就地工作,否则创建一个副本。这是通过分派到 namespace internal
中的适当版本来实现的。如果容器的 value_type
不能被覆盖,则应禁用右值版本 - 例如 std::pair<const int, int>
- 即使容器是右值。
以下代码 works as expected 带有 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
}
但是 not compile on MSVC++ and 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++
查看
我希望它适用于不同的容器和不同种类的对象,例如std::vector<double>
、std::map<int, std::string>
等。std::map
案例(使用different inserter)是我遇到value_types
of std::pair<const T, U>
问题的情况.
您是否知道如何更改 dispatch / sfinae 以使其也适用于 MSVC++(ver.MSVC++ 2017 15.2 26430.6 在我的例子中)和向下的 GCC 6.2?
您没有说明您使用的是哪个版本的 Visual C++ 编译器,但您是否尝试过使用尾随 return 语法?
换句话说,替换为:
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 工作,并且不得不解决一些 "should" 工作但不工作的事情。
我承认这有点猜测,但您可以试一试。
如果您希望您的代码专门用于类似对象的元组容器,您可以这样做(残酷但适用于旧版本的 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
}
问题似乎是 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 example and other live example.
我还将您的 SFINAE 调度更改为基于标签的调度。
其他 operator=
禁用有缺陷的类型可能也需要 can_self_assign
专业化。值得注意的示例可能包括 tuple<Ts...>
和 vector<T,A>
以及类似的。
我不知道编译器何时以及是否需要 operator=
"not exist" 如果它在 std
类型中不起作用;我记得 std::vector
曾经没有要求,但我还记得有一项提案添加了此类要求。
很好的发现,在这些更改得到修复之前,我会提供针对这个极端情况的解决方案。
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
}
这只是引入了 is_assignable
对类型的特化,正如 the defect report 中所建议的那样。我添加的另一件事是 std::is_rvalue_reference
以防止对左值引用的调用(在你的解决方案中它被 Container::value_type
中的失败替换禁用,当 Container
是 vector<...>&
.