C++ 用 SFINAE 重载泛型函数
C++ overloading generic function with SFINAE
我正在尝试编写一个简单的序列化协议。容器类型的 send
应该在每个元素(它们本身可能是容器类型)上调用 send
。
但是我不知道该怎么做。我已经编写了三个函数,每个函数都单独工作,但我无法弄清楚如何使用 C++ select 并分派适当的函数。我该怎么做?
就目前而言,clang 说我不能用相同的签名重新定义 send
(甚至不能为了 SFINAE)。
我正在使用 C++17,但我无法使用外部库,例如 boost。 [编辑] 这是个人挑战。我想知道如何使用 vanilla C++ 做到这一点。毕竟,该库必须由某人用普通 C++ 编写,对吗? [/编辑]
#include <vector>
#include <utility>
#include <iostream>
#include <type_traits>
template <class pair_t>
std::void_t<typename pair_t::first_type, typename pair_t::second_type>
send(pair_t pair) {
using first_t = typename pair_t::first_type;
using second_t = typename pair_t::second_type;
send<first_t>(std::get<0>(pair));
send<second_t>(std::get<1>(pair));
}
template <typename list_t>
std::void_t<typename list_t::value_type, typename list_t::size_type>
send(list_t list) {
using value_t = typename list_t::value_type;
for (const value_t& elem : list) {
send<value_t>(elem);
}
}
template <typename int_t>
void
send(typename std::enable_if<std::is_integral<int_t>::value, int_t>::type val) {
std::cout << "Sending integral val: " << val << std::endl;
}
int obj0 = 1;
std::vector<int> obj1;
std::pair<int, int> obj2;
std::vector<std::pair<int,int>> obj3;
int main() {
// send<int>(obj0);
// send(obj1);
// send(obj2);
// send(obj3);
}
你的问题是,就编译器而言,你对 pair 和 list 的声明如下所示:
template <typename T>
blah send(T arg) {
...
}
...并且在该签名中没有使用 enable_if
。我认为您需要将 enable_if
放入参数类型中。 (我也会通过 const 引用获取 list_t
- 你不想不必要地复制列表)。
您可以使用模板专业化:
template <typename T> void send(const T&);
namespace detail {
template <typename T> struct Send; // primary template
template <typename T> struct Send<std::vector<T>> { // specialization for vector
static void send(const std::vector<T>& v) {
for (const auto e : v) ::send(e);
}
};
template <typename T, typename U> struct Send<std::pair<T, U>> { // for pair
static void send(const std::pair<T, U>& p) {
::send(p.first); ::send(p.second);
}
};
template <> struct Send<int> { // for int
static void send(int i) { std::cout << i; }
};
}
template <typename T> void send(const T& arg) { detail::Send<T>::send(arg); }
int main() {
int i = 0;
std::vector<int> vi = { 1, 2, 3 };
std::pair<int, int> pi = { 4, 5 };
std::vector<std::pair<int, int>> vpi = { { 7, 8 }, { 9, 0 } };
send(i);
send(vi);
send(pi);
send(vpi);
}
现场演示:https://wandbox.org/permlink/cQNpsgSFYmiurqRT
请注意,此解决方案需要对您要支持的所有类型的容器进行专门化处理(这与 对嵌套 value_type
和 [=14= 的任何类型使用此实现不同) ]).
或者,您也可以选择 SFINAE:
template <typename T> struct is_vector : std::false_type { };
template <typename T> struct is_vector<std::vector<T>> : std::true_type { };
template <typename T> struct is_pair : std::false_type { };
template <typename T, typename U> struct is_pair<std::pair<T, U>> : std::true_type { };
template <typename T> void send(T i, std::enable_if_t<std::is_integral_v<T>, int> = 0) {
std::cout << i;
}
template <typename T> void send(const T& p, std::enable_if_t<is_pair<T>::value, int> = 0) {
send(p.first); send(p.second);
}
template <typename T> void send(const T& v, std::enable_if_t<is_vector<T>::value, int> = 0) {
for (const auto & e : v) send(e);
}
... // same main
或者,您也可以按如下方式检查成员类型是否存在:
template <typename T> struct has_value_type {
using yes = char[1]; using no = char[2];
template <typename C> static constexpr yes& test(typename C::value_type*);
template <typename> static constexpr no& test(...);
static constexpr bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
};
... // similarly: has_size_type, has_first_type, and has_second_type
template <typename T> void send(T i, std::enable_if_t<std::is_integral_v<T>, int> = 0) {
std::cout << i;
}
template <typename T> void send(const T& p, std::enable_if_t<has_first_type<T>::value && has_second_type<T>::value, int> = 0) {
send(p.first); send(p.second);
}
template <typename T> void send(const T& v, std::enable_if_t<has_value_type<T>::value && has_size_type<T>::value, int> = 0) {
for (const auto & e : v) send(e);
}
... // same main
到目前为止,解决这个问题最简单的方法是标签分派。
首先为您的属性编写特征。
template<class T, class=void>
struct is_pair; // inherits from true_type or false_type depending on if T is a pair.
template<class T, class=void>
struct is_list;
现在我们派遣。
namespace impl {
template <class pair_t>
void send(pair_t pair, std::true_type /* is pair */, std::false_type, std::false_type) {
using first_t = typename pair_t::first_type;
using second_t = typename pair_t::second_type;
send<first_t>(std::get<0>(pair));
send<second_t>(std::get<1>(pair));
}
template <typename list_t>
void send(list_t list, std::false_type, std::true_type /* list */, std::false_type) {
using value_t = typename list_t::value_type;
for (const value_t& elem : list) {
send<value_t>(elem);
}
}
template <typename int_t>
void send(int_t, std::false_type, std::false_type, std::true_type /* is integral */) {
std::cout << "Sending integral val: " << val << std::endl;
}
}
template<class T>
auto send( T t )
-> decltype( impl::send( std::move(t), is_pair<T>{}, is_list<T>{}, std::is_integral<T>{} ) )
{ impl::send( std::move(t), is_pair<T>{}, is_list<T>{}, std::is_integral<T>{} ); }
我正在尝试编写一个简单的序列化协议。容器类型的 send
应该在每个元素(它们本身可能是容器类型)上调用 send
。
但是我不知道该怎么做。我已经编写了三个函数,每个函数都单独工作,但我无法弄清楚如何使用 C++ select 并分派适当的函数。我该怎么做?
就目前而言,clang 说我不能用相同的签名重新定义 send
(甚至不能为了 SFINAE)。
我正在使用 C++17,但我无法使用外部库,例如 boost。 [编辑] 这是个人挑战。我想知道如何使用 vanilla C++ 做到这一点。毕竟,该库必须由某人用普通 C++ 编写,对吗? [/编辑]
#include <vector>
#include <utility>
#include <iostream>
#include <type_traits>
template <class pair_t>
std::void_t<typename pair_t::first_type, typename pair_t::second_type>
send(pair_t pair) {
using first_t = typename pair_t::first_type;
using second_t = typename pair_t::second_type;
send<first_t>(std::get<0>(pair));
send<second_t>(std::get<1>(pair));
}
template <typename list_t>
std::void_t<typename list_t::value_type, typename list_t::size_type>
send(list_t list) {
using value_t = typename list_t::value_type;
for (const value_t& elem : list) {
send<value_t>(elem);
}
}
template <typename int_t>
void
send(typename std::enable_if<std::is_integral<int_t>::value, int_t>::type val) {
std::cout << "Sending integral val: " << val << std::endl;
}
int obj0 = 1;
std::vector<int> obj1;
std::pair<int, int> obj2;
std::vector<std::pair<int,int>> obj3;
int main() {
// send<int>(obj0);
// send(obj1);
// send(obj2);
// send(obj3);
}
你的问题是,就编译器而言,你对 pair 和 list 的声明如下所示:
template <typename T>
blah send(T arg) {
...
}
...并且在该签名中没有使用 enable_if
。我认为您需要将 enable_if
放入参数类型中。 (我也会通过 const 引用获取 list_t
- 你不想不必要地复制列表)。
您可以使用模板专业化:
template <typename T> void send(const T&);
namespace detail {
template <typename T> struct Send; // primary template
template <typename T> struct Send<std::vector<T>> { // specialization for vector
static void send(const std::vector<T>& v) {
for (const auto e : v) ::send(e);
}
};
template <typename T, typename U> struct Send<std::pair<T, U>> { // for pair
static void send(const std::pair<T, U>& p) {
::send(p.first); ::send(p.second);
}
};
template <> struct Send<int> { // for int
static void send(int i) { std::cout << i; }
};
}
template <typename T> void send(const T& arg) { detail::Send<T>::send(arg); }
int main() {
int i = 0;
std::vector<int> vi = { 1, 2, 3 };
std::pair<int, int> pi = { 4, 5 };
std::vector<std::pair<int, int>> vpi = { { 7, 8 }, { 9, 0 } };
send(i);
send(vi);
send(pi);
send(vpi);
}
现场演示:https://wandbox.org/permlink/cQNpsgSFYmiurqRT
请注意,此解决方案需要对您要支持的所有类型的容器进行专门化处理(这与 对嵌套 value_type
和 [=14= 的任何类型使用此实现不同) ]).
或者,您也可以选择 SFINAE:
template <typename T> struct is_vector : std::false_type { };
template <typename T> struct is_vector<std::vector<T>> : std::true_type { };
template <typename T> struct is_pair : std::false_type { };
template <typename T, typename U> struct is_pair<std::pair<T, U>> : std::true_type { };
template <typename T> void send(T i, std::enable_if_t<std::is_integral_v<T>, int> = 0) {
std::cout << i;
}
template <typename T> void send(const T& p, std::enable_if_t<is_pair<T>::value, int> = 0) {
send(p.first); send(p.second);
}
template <typename T> void send(const T& v, std::enable_if_t<is_vector<T>::value, int> = 0) {
for (const auto & e : v) send(e);
}
... // same main
或者,您也可以按如下方式检查成员类型是否存在:
template <typename T> struct has_value_type {
using yes = char[1]; using no = char[2];
template <typename C> static constexpr yes& test(typename C::value_type*);
template <typename> static constexpr no& test(...);
static constexpr bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
};
... // similarly: has_size_type, has_first_type, and has_second_type
template <typename T> void send(T i, std::enable_if_t<std::is_integral_v<T>, int> = 0) {
std::cout << i;
}
template <typename T> void send(const T& p, std::enable_if_t<has_first_type<T>::value && has_second_type<T>::value, int> = 0) {
send(p.first); send(p.second);
}
template <typename T> void send(const T& v, std::enable_if_t<has_value_type<T>::value && has_size_type<T>::value, int> = 0) {
for (const auto & e : v) send(e);
}
... // same main
到目前为止,解决这个问题最简单的方法是标签分派。
首先为您的属性编写特征。
template<class T, class=void>
struct is_pair; // inherits from true_type or false_type depending on if T is a pair.
template<class T, class=void>
struct is_list;
现在我们派遣。
namespace impl {
template <class pair_t>
void send(pair_t pair, std::true_type /* is pair */, std::false_type, std::false_type) {
using first_t = typename pair_t::first_type;
using second_t = typename pair_t::second_type;
send<first_t>(std::get<0>(pair));
send<second_t>(std::get<1>(pair));
}
template <typename list_t>
void send(list_t list, std::false_type, std::true_type /* list */, std::false_type) {
using value_t = typename list_t::value_type;
for (const value_t& elem : list) {
send<value_t>(elem);
}
}
template <typename int_t>
void send(int_t, std::false_type, std::false_type, std::true_type /* is integral */) {
std::cout << "Sending integral val: " << val << std::endl;
}
}
template<class T>
auto send( T t )
-> decltype( impl::send( std::move(t), is_pair<T>{}, is_list<T>{}, std::is_integral<T>{} ) )
{ impl::send( std::move(t), is_pair<T>{}, is_list<T>{}, std::is_integral<T>{} ); }