不同类型的专业化
Specializations for different types
谁能告诉我如何删除下面重复的专业化?
#include <iostream>
#include <fstream>
#include <string>
struct Thing {
int a, b;
void load (std::istream& is) {is >> std::skipws >> a >> b;}
};
struct Object {
int a, b, c;
void load (std::istream& is) {is >> std::skipws >> a >> b >> c;}
};
template <typename...> struct PassArgs;
// General case.
template <typename First, typename... Rest>
struct PassArgs<First, Rest...> : PassArgs<Rest...> {
void operator()(std::istream& is, First& first, Rest&... rest) const {
is >> first;
PassArgs<Rest...>::operator()(is, rest...);
}
};
// Specialization for std::string needed.
template <typename... Rest>
struct PassArgs<std::string, Rest...> : PassArgs<Rest...> {
void operator()(std::istream& is, std::string& first, Rest&... rest) const {
while (std::getline (is, first) && first.empty());
PassArgs<Rest...>::operator()(is, rest...);
}
};
// Specialization for class Thing.
template <typename... Rest>
struct PassArgs<Thing, Rest...> : PassArgs<Rest...> {
void operator()(std::istream& is, Thing& first, Rest&... rest) const {
first.load(is);
PassArgs<Rest...>::operator()(is, rest...);
}
};
// Specialization for class Object, but is the exact same as that for Thing.
template <typename... Rest>
struct PassArgs<Object, Rest...> : PassArgs<Rest...> {
void operator()(std::istream& is, Object& first, Rest&... rest) const {
first.load(is);
PassArgs<Rest...>::operator()(is, rest...);
}
};
template <>
struct PassArgs<> {
void operator()(std::istream&) const {} // End of recursion.
};
int main() {}
一切正常,但有没有办法避免对所有具有 load(std::istream&)
函数的 类 进行特化(我的程序中有很多)。目前,我有 Thing
、Object
和许多其他 类 的专长,它们的专长都相同。
顺带一提,客户端是这样使用的PassArgs
:
template <typename T, typename... Args>
T* create (std::istream& is, Args&... args) {
PassArgs<Args...>()(is, args...);
T* t = new T(args...);
// Do whatever with t;
return t;
}
大约有无数种方法可以做到这一点。这是其中之一。
首先是一个trait,检测是否有成员load()
被调用。这是一种写法:
namespace details {
template<class T>
auto has_load_impl(int)
-> decltype((void)std::declval<T&>().load(std::declval<std::istream&>()),
std::true_type());
template<class T>
std::false_type has_load_impl(...);
template<class T>
using has_load = decltype(has_load_impl<T>(0));
}
还有很多其他方法可以写出这个特征。例如,Jonathan Wakely 的回答使用了 Walter Brown 的 void_t
。或者你可以使用 std::experimental::is_detected
.
接下来,编写一个加载单个参数的函数,根据 has_load
的结果进行调度。这是一种方法:
namespace details {
template<class T>
void do_load(std::istream& is, T& t, std::true_type /*has_load*/){
t.load(is);
}
template<class T>
void do_load(std::istream& is, T& t, std::false_type /*has_load*/){
is >> t;
}
}
template<class T>
void load(std::istream& is, T& t){
details::do_load(is, t, details::has_load<T>());
}
如果您在其他地方不需要该特征,也可以直接在 do_load
函数中省去 standalone 特征和 SFINAE:
namespace details {
template<class T>
auto do_load(std::istream& is, T& t, int) -> decltype((void)t.load(is)){
t.load(is);
}
template<class T>
void do_load(std::istream& is, T& t, ...){
is >> t;
}
}
template<class T>
void load(std::istream& is, T& t){
details::do_load(is, t, 0);
}
根据需要为需要特殊处理的类型添加重载。
void load(std::istream& is, std::string& s){
while (std::getline (is, s) && s.empty());
}
最后,PassArgs
本身可以简化为两行函数,在 braced-init-list 中使用熟悉的包扩展技巧:
template<class... Args>
void PassArgs(std::istream& is, Args&... args){
using expander = int[];
(void)expander{0, (load(is, args), void(), 0)... };
}
Demo.
在上面,用户可以使用ADL自定义load
。或者,您可以使 load
成为 class 模板 loader<T>
的成员函数,用户可以根据需要专门化 loader
来自定义负载:
template<class T>
struct loader {
static void load(std::istream& is, T& t) {
details::do_load(is, t, details::has_load<T>());
}
};
template<> struct loader<std::string>{
static void load(std::istream& is, std::string& s){
while (std::getline (is, s) && s.empty());
}
};
template<class... Args>
void PassArgs(std::istream& is, Args&... args){
using expander = int[];
(void)expander{0, (loader<T>::load(is, args), void(), 0)... };
}
定义一个特征来检测 load
成员:
template<typename T> using void_t = void;
template<typename T, typename = void_t<>>
struct has_load
: std::false_type { };
template<typename T>
struct has_load<T, void_t<decltype(std::declval<T&>().load(std::declval<std::istream&>()))>>
: std::true_type
{ };
将单个类型的实际加载提升到单独的 class 模板中,专门用于具有 load
成员(使用特征)的类型:
template<typename T, bool use_load = has_load<T>::value>
struct PassArg
{
static void pass(std::istream& is, T& t) { is >> t; }
};
template<typename T>
struct PassArg<T, true>
{
static void pass(std::istream& is, T& t) { t.load(is); }
};
然后在您的主模板中使用它:
// General case.
template <typename First, typename... Rest>
struct PassArgs : PassArgs<Rest...> {
void operator()(std::istream& is, First& first, Rest&... rest) const
{
PassArg<First>::pass(is, first);
PassArgs<Rest...>::operator()(is, rest...);
}
};
string
案例也可以通过特化 PassArg
来完成。
template<>
struct PassArg<std::string, false>
{
static void pass(std::istream& is, std::string& s)
{ getline(is, s); }
};
N.B。我让新的 class 有一个名为 pass
而不是 operator()
的静态函数,因为如果你发现自己在写这个:
PassArgs<Args...>()(is, args...);
或更糟,像这样按名称调用 operator()
:
PassArgs<Rest...>::operator()(is, rest...);
那么你可能不需要仿函数。 PassArgs
是无状态的,因此没有必要创建它的实例,如果您必须明确命名 operator()
那么您做错了。给函数一个合适的名称并调用它,并使其成为静态的:
PassArgs<Rest...>::sensible_name(is, rest...);
我将一直到 ADL 解决方案。
首先,这里是friend
基于负载的支持。
struct Thing {
int a, b;
friend void load (std::istream& is, Thing& t) {is >> std::skipws >> t.a >> t.b;}
};
基于成员的负载:
struct Object {
int a, b, c;
void load (std::istream& is) {is >> std::skipws >> a >> b >> c;}
};
首先,一些元编程样板。你可以在没有样板的情况下用更少的行来做到这一点,但它使它更干净:
namespace meta {
namespace details {
template<template<class...>class Z, class=void, class...Ts>
struct can_apply : std::false_type {};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, decltype((void)(std::declval<Z<Ts...>>())), Ts...>:
std::true_type
{};
}
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z,void,Ts...>;
}
template<class T>
using member_load = decltype( std::declval<T>().load(std::declval<std::istream&>()) );
template<class T>
using stream_load = decltype( std::declval<std::istream&>() >> std::declval<T>() );
引出这个妙语:
template<class T>
using has_member_load = meta::can_apply< member_load, T >;
template<class T>
using has_stream_load = meta::can_apply< stream_load, T >;
现在我们创建一个 loading
命名空间:
namespace loading {
void load(std::istream&is, std::string& s) {
while (std::getline (is, s) && s.empty());
}
template<class T>
std::enable_if_t<has_member_load<T&>::value>
load(std::istream&is, T& t) {
t.load(is);
}
// uses ... to keep lowest priority:
template<class T>
std::enable_if_t<has_stream_load<T&>::value>
load(std::istream& is, T& t, ...) {
is >> t;
}
template<class...Ts>
void load_many(std::istream&is, Ts&...ts) {
using discard=int[];
(void)discard{0,((
load(is, ts)
),void(),0)...};
}
}
我们现在可以调用 loading::load_many(is, a, b, c, d)
.
std::string
有一个自定义的 loading::load
函数,您想要支持的 std
中的任何其他特定类型也可以。例如,你可以在namespace loading
中写template<class T, class A> void load(std::istream& is, std::vector<T,A>&)
,这样就可以了。
任何 class X 在其名称空间中定义了一个自由函数 load(istream&, X&)
时将调用该函数。
否则,任何具有 .load(istream&)
方法的 class 都将调用该方法。
任何 class X 具有 istream& >> X&
重载,如果上述所有操作均失败,则会被调用。
谁能告诉我如何删除下面重复的专业化?
#include <iostream>
#include <fstream>
#include <string>
struct Thing {
int a, b;
void load (std::istream& is) {is >> std::skipws >> a >> b;}
};
struct Object {
int a, b, c;
void load (std::istream& is) {is >> std::skipws >> a >> b >> c;}
};
template <typename...> struct PassArgs;
// General case.
template <typename First, typename... Rest>
struct PassArgs<First, Rest...> : PassArgs<Rest...> {
void operator()(std::istream& is, First& first, Rest&... rest) const {
is >> first;
PassArgs<Rest...>::operator()(is, rest...);
}
};
// Specialization for std::string needed.
template <typename... Rest>
struct PassArgs<std::string, Rest...> : PassArgs<Rest...> {
void operator()(std::istream& is, std::string& first, Rest&... rest) const {
while (std::getline (is, first) && first.empty());
PassArgs<Rest...>::operator()(is, rest...);
}
};
// Specialization for class Thing.
template <typename... Rest>
struct PassArgs<Thing, Rest...> : PassArgs<Rest...> {
void operator()(std::istream& is, Thing& first, Rest&... rest) const {
first.load(is);
PassArgs<Rest...>::operator()(is, rest...);
}
};
// Specialization for class Object, but is the exact same as that for Thing.
template <typename... Rest>
struct PassArgs<Object, Rest...> : PassArgs<Rest...> {
void operator()(std::istream& is, Object& first, Rest&... rest) const {
first.load(is);
PassArgs<Rest...>::operator()(is, rest...);
}
};
template <>
struct PassArgs<> {
void operator()(std::istream&) const {} // End of recursion.
};
int main() {}
一切正常,但有没有办法避免对所有具有 load(std::istream&)
函数的 类 进行特化(我的程序中有很多)。目前,我有 Thing
、Object
和许多其他 类 的专长,它们的专长都相同。
顺带一提,客户端是这样使用的PassArgs
:
template <typename T, typename... Args>
T* create (std::istream& is, Args&... args) {
PassArgs<Args...>()(is, args...);
T* t = new T(args...);
// Do whatever with t;
return t;
}
大约有无数种方法可以做到这一点。这是其中之一。
首先是一个trait,检测是否有成员load()
被调用。这是一种写法:
namespace details {
template<class T>
auto has_load_impl(int)
-> decltype((void)std::declval<T&>().load(std::declval<std::istream&>()),
std::true_type());
template<class T>
std::false_type has_load_impl(...);
template<class T>
using has_load = decltype(has_load_impl<T>(0));
}
还有很多其他方法可以写出这个特征。例如,Jonathan Wakely 的回答使用了 Walter Brown 的 void_t
。或者你可以使用 std::experimental::is_detected
.
接下来,编写一个加载单个参数的函数,根据 has_load
的结果进行调度。这是一种方法:
namespace details {
template<class T>
void do_load(std::istream& is, T& t, std::true_type /*has_load*/){
t.load(is);
}
template<class T>
void do_load(std::istream& is, T& t, std::false_type /*has_load*/){
is >> t;
}
}
template<class T>
void load(std::istream& is, T& t){
details::do_load(is, t, details::has_load<T>());
}
如果您在其他地方不需要该特征,也可以直接在 do_load
函数中省去 standalone 特征和 SFINAE:
namespace details {
template<class T>
auto do_load(std::istream& is, T& t, int) -> decltype((void)t.load(is)){
t.load(is);
}
template<class T>
void do_load(std::istream& is, T& t, ...){
is >> t;
}
}
template<class T>
void load(std::istream& is, T& t){
details::do_load(is, t, 0);
}
根据需要为需要特殊处理的类型添加重载。
void load(std::istream& is, std::string& s){
while (std::getline (is, s) && s.empty());
}
最后,PassArgs
本身可以简化为两行函数,在 braced-init-list 中使用熟悉的包扩展技巧:
template<class... Args>
void PassArgs(std::istream& is, Args&... args){
using expander = int[];
(void)expander{0, (load(is, args), void(), 0)... };
}
Demo.
在上面,用户可以使用ADL自定义load
。或者,您可以使 load
成为 class 模板 loader<T>
的成员函数,用户可以根据需要专门化 loader
来自定义负载:
template<class T>
struct loader {
static void load(std::istream& is, T& t) {
details::do_load(is, t, details::has_load<T>());
}
};
template<> struct loader<std::string>{
static void load(std::istream& is, std::string& s){
while (std::getline (is, s) && s.empty());
}
};
template<class... Args>
void PassArgs(std::istream& is, Args&... args){
using expander = int[];
(void)expander{0, (loader<T>::load(is, args), void(), 0)... };
}
定义一个特征来检测 load
成员:
template<typename T> using void_t = void;
template<typename T, typename = void_t<>>
struct has_load
: std::false_type { };
template<typename T>
struct has_load<T, void_t<decltype(std::declval<T&>().load(std::declval<std::istream&>()))>>
: std::true_type
{ };
将单个类型的实际加载提升到单独的 class 模板中,专门用于具有 load
成员(使用特征)的类型:
template<typename T, bool use_load = has_load<T>::value>
struct PassArg
{
static void pass(std::istream& is, T& t) { is >> t; }
};
template<typename T>
struct PassArg<T, true>
{
static void pass(std::istream& is, T& t) { t.load(is); }
};
然后在您的主模板中使用它:
// General case.
template <typename First, typename... Rest>
struct PassArgs : PassArgs<Rest...> {
void operator()(std::istream& is, First& first, Rest&... rest) const
{
PassArg<First>::pass(is, first);
PassArgs<Rest...>::operator()(is, rest...);
}
};
string
案例也可以通过特化 PassArg
来完成。
template<>
struct PassArg<std::string, false>
{
static void pass(std::istream& is, std::string& s)
{ getline(is, s); }
};
N.B。我让新的 class 有一个名为 pass
而不是 operator()
的静态函数,因为如果你发现自己在写这个:
PassArgs<Args...>()(is, args...);
或更糟,像这样按名称调用 operator()
:
PassArgs<Rest...>::operator()(is, rest...);
那么你可能不需要仿函数。 PassArgs
是无状态的,因此没有必要创建它的实例,如果您必须明确命名 operator()
那么您做错了。给函数一个合适的名称并调用它,并使其成为静态的:
PassArgs<Rest...>::sensible_name(is, rest...);
我将一直到 ADL 解决方案。
首先,这里是friend
基于负载的支持。
struct Thing {
int a, b;
friend void load (std::istream& is, Thing& t) {is >> std::skipws >> t.a >> t.b;}
};
基于成员的负载:
struct Object {
int a, b, c;
void load (std::istream& is) {is >> std::skipws >> a >> b >> c;}
};
首先,一些元编程样板。你可以在没有样板的情况下用更少的行来做到这一点,但它使它更干净:
namespace meta {
namespace details {
template<template<class...>class Z, class=void, class...Ts>
struct can_apply : std::false_type {};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, decltype((void)(std::declval<Z<Ts...>>())), Ts...>:
std::true_type
{};
}
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z,void,Ts...>;
}
template<class T>
using member_load = decltype( std::declval<T>().load(std::declval<std::istream&>()) );
template<class T>
using stream_load = decltype( std::declval<std::istream&>() >> std::declval<T>() );
引出这个妙语:
template<class T>
using has_member_load = meta::can_apply< member_load, T >;
template<class T>
using has_stream_load = meta::can_apply< stream_load, T >;
现在我们创建一个 loading
命名空间:
namespace loading {
void load(std::istream&is, std::string& s) {
while (std::getline (is, s) && s.empty());
}
template<class T>
std::enable_if_t<has_member_load<T&>::value>
load(std::istream&is, T& t) {
t.load(is);
}
// uses ... to keep lowest priority:
template<class T>
std::enable_if_t<has_stream_load<T&>::value>
load(std::istream& is, T& t, ...) {
is >> t;
}
template<class...Ts>
void load_many(std::istream&is, Ts&...ts) {
using discard=int[];
(void)discard{0,((
load(is, ts)
),void(),0)...};
}
}
我们现在可以调用 loading::load_many(is, a, b, c, d)
.
std::string
有一个自定义的 loading::load
函数,您想要支持的 std
中的任何其他特定类型也可以。例如,你可以在namespace loading
中写template<class T, class A> void load(std::istream& is, std::vector<T,A>&)
,这样就可以了。
任何 class X 在其名称空间中定义了一个自由函数 load(istream&, X&)
时将调用该函数。
否则,任何具有 .load(istream&)
方法的 class 都将调用该方法。
任何 class X 具有 istream& >> X&
重载,如果上述所有操作均失败,则会被调用。