具有模板化 class 作为 return 值的 C++ 模板函数
C++ templated function with templated class as return value
我有这个 Java 代码:
public interface Adapter<FROM,TO> {
/**
* Adapts something from one type to a type.
* @param f
* @return
*/
public TO adapt(FROM f);
/**
* Chains adapters.
*
* @param <NEXT>
* @param next
* @return a new adapter that takes the output of this adapter and
* adapts to something of type NEXT.
*/
public default <NEXT> Adapter<FROM,NEXT> chain(Adapter<TO,NEXT> next){
Adapter<FROM,TO> x=this;
return new Adapter<FROM,NEXT>(){
@Override
public NEXT adapt(FROM f) {
return next.adapt(x.adapt(f));
}
};
}
我真的很喜欢 C++ 中类似的东西。到目前为止我做的最好的是:
#ifndef ADAPTER_H
#define ADAPTER_H
template <typename FROM, typename TO>
class Adapter
{
public:
TO adapt(FROM f);
template <typename NEXT> Adapter<FROM, NEXT> chain(Adapter<TO, NEXT> arg)
{
class UnNamed: public Adapter<FROM, NEXT>
{
public:
Adapter<TO, NEXT> outer;
UnNamed(Adapter<TO, NEXT> arg)
{
outer = arg;
}
NEXT adapt(FROM from)
{
outer.adapt(Adapter::adapt(from));
}
};
return UnNamed(arg);
}
};
#endif
但它因未定义的引用错误而失败。我真的很想让 api 尽可能靠近 Java,但我不知道如何让嵌套的 class 以可参考的方式存在。
你很接近。
在Java中,interface
s必须由具体的classes实现,并且interface
方法是虚拟的,必须在class中重写]是的。在您的示例中,在 chain()
方法上使用 default
关键字允许 Java 生成自己的 class 以实现 Adapter
接口chain()
returns.
在C++中,你可以做类似的事情,除了没有等价于Java的default
关键字(C++有一个default
关键字,但它的意思很不同),所以你需要显式定义你自己的 class 类型来实现你的虚拟接口。您尝试这样做,但是多态性不起作用,除非您通过 pointers/references 调用对象的虚拟方法,否则您可能会遇到 object slicing。在某种程度上,Java 也强制执行此规则,因为 Java 中的所有对象都是引用类型,只有基本类型是值类型(而在 C++ 中,classes 也可以用作值类型)。
尝试更像这样的东西:
#include <memory>
template<typename FROM, typename TO>
class Adapter
{
public:
/**
* Adapts something from one type to a type.
* @param f
* @return
*/
virtual TO adapt(FROM f) = 0;
/**
* Chains adapters.
*
* @param <NEXT>
* @param next
* @return a new adapter that takes the output of this adapter and
* adapts to something of type NEXT.
*/
template<typename NEXT>
std::unique_ptr<Adapter<FROM, NEXT>> chain(Adapter<TO, NEXT> *next)
{
class ChainedAdapter : public Adapter<FROM, NEXT>
{
private:
Adapter<FROM, TO> *x;
Adapter<TO, NEXT> *next;
public:
ChainedAdapter(Adapter<FROM, TO> *x, Adapter<TO, NEXT> *next)
: x(x), next(next) {}
NEXT adapt(FROM f) override {
return next->adapt(x->adapt(f));
}
};
return std::make_unique<ChainedAdapter>(this, next);
}
};
虽然,我想你也可以不用动态创建 chain()
return 一个新的 ChainedAdapter
,如果你对 return 非常小心的话ed 对象并且不要以任何会对其进行切片的方式使用它:
#include <memory>
template<typename FROM, typename TO>
class Adapter
{
public:
/**
* Adapts something from one type to a type.
* @param f
* @return
*/
virtual TO adapt(FROM f) = 0;
/**
* Chains adapters.
*
* @param <NEXT>
* @param next
* @return a new adapter that takes the output of this adapter and
* adapts to something of type NEXT.
*/
template<typename NEXT>
auto chain(Adapter<TO, NEXT> *next)
{
class ChainedAdapter : public Adapter<FROM, NEXT>
{
private:
Adapter<FROM, TO> *x;
Adapter<TO, NEXT> *next;
public:
ChainedAdapter(Adapter<FROM, TO> *x, Adapter<TO, NEXT> *next)
: x(x), next(next) {}
NEXT adapt(FROM f) override {
return next->adapt(x->adapt(f));
}
};
return ChainedAdapter(this, next);
}
};
我会避免直接 Java 代码翻译。
一种 C++ 方法是拥抱价值观。
template<class Sig>
struct adapter;
template<class Out, class In>
struct adapter<Out(In)>:std::function<Out(In)>
{
using std::function<Out(In)>::function;
template<class Next, class Result=std::invoke_result_t<Next const&, Out>>
adapter<Result(In)> chain( Next next ) const& {
auto tmp=*this;
return std::move(tmp).chain(std::move(next));
}
template<class Next, class Result=std::invoke_result_t<Next, Out>>
adapter<Result(In)> chain( Next next ) && {
return [self=std::move(*this), next=std::move(next)](In in)->Result {
return next(self(std::forward<In>(in)));
};
}
};
我们开始了。
adapter<int(double)> rounder=[](double d){return std::floor(d);};
adapter<double(std::istream&)> double_reader=[](auto&is){double d; is>>d; return d;};
adapter<int(std::istream&)> int_reader=double_reader.chain(rounder);
std::cout << int_reader(std::cin);
等等
这些适配器是多态值类型。您可以将它们与函数指针、lambda、std 函数或其他函数对象链接起来
上面代码中的x.adapt(foo)
调用拼写为x(foo)
。主要目的是被调用的对象...可以使用 operator()
使 then 可调用。
设计:
我让 std::function
完成了大部分繁重的工作。它是一种支持调用和可空性的多态值类型。
我们简单地继承它,转发构造函数,并添加一个 .chain
方法来检查兼容性并推导 return 类型。
我们可以扩展适配器以轻松支持多对一适配。第一步是通过在正确的位置添加一堆 ...
来支持多个输入:
template<class Sig>
struct adapter;
template<class Out, class... In>
struct adapter<Out(In...)>:std::function<Out(In...)>
{
using std::function<Out(In...)>::function;
template<class Next, class Result=std::invoke_result_t<Next const&, Out>>
adapter<Result(In...)> chain( Next next ) const& {
auto tmp=*this;
return std::move(tmp).chain(std::move(next));
}
template<class Next, class Result=std::invoke_result_t<Next, Out>>
adapter<Result(In...)> chain( Next next ) && {
return [self=std::move(*this), next=std::move(next)](In... in)->Result {
return next(self(std::forward<In>(in)...));
};
}
};
现在,first.chain(second)
语法不适用于 second
的多个输入。我们可以添加另一种方法:
template<class Sig>
struct adapter;
template<class Out, class... In>
struct adapter<Out(In...)>:std::function<Out(In...)>
{
using std::function<Out(In...)>::function;
template<class Next, class Result=std::invoke_result_t<Next const&, Out>>
adapter<Result(In...)> chain( Next next ) const& {
auto tmp=*this;
return std::move(tmp).chain(std::move(next));
}
template<class Next, class Result=std::invoke_result_t<Next, Out>>
adapter<Result(In...)> chain( Next next ) && {
return [self=std::move(*this), next=std::move(next)](In... in)->Result {
return next(self(std::forward<In>(in)...));
};
}
template<class...First>
adapter<Out(First...)> consume( adapter<In(First)>... src)&&
{
return [self=std::move(*this), ...src=std::move(src)](First... first)->Out
{
return self(src(std::forward<First>(first))...);
};
}
template<class...First>
adapter<Out(First...)> consume( adapter<In(First)>... src) const&
{
auto tmp = *this;
return std::move(tmp).consume( std::move(src)... );
}
};
但这有点远了,不是吗?
有些人对以非多态方式从值类型(如 std::function
)继承持怀疑态度。在这里,我认为几乎没有人在他们的头脑中存储指向 std::function
s 的指针;那些这样做的,通常是在做 shared_ptr
技巧(处理多态破坏)。
但是,如果您担心,可以这样做:
template<class Out, class... In>
struct adapter<Out(In...)>
{
using F = std::function<Out(In...)>;
F f;
// forward call operator
template<class...Args>
auto operator()(Args&&...args)const
-> std::invoke_result_t<F const&, Args&&...>
{
return f(std::forward<Args>(args)...);
}
// default all special member functions:
adapter()=default;
adapter(adapter const&)=default;
adapter(adapter &&)=default;
adapter& operator=(adapter const&)=default;
adapter& operator=(adapter &&)=default;
~adapter()=default;
// forward a few useful operations and ctors:
explicit operator bool() const { return (bool)f; }
template<class Fin>
requires (!std::is_same_v<Fin, adapter> && std::convertible_to<Fin, F>)
adapter( Fin fin ):f(std::forward<Fin>(fin)) {}
然后添加 .chain
方法。
如您所见,这增加了一些代码。 Live example.
我有这个 Java 代码:
public interface Adapter<FROM,TO> {
/**
* Adapts something from one type to a type.
* @param f
* @return
*/
public TO adapt(FROM f);
/**
* Chains adapters.
*
* @param <NEXT>
* @param next
* @return a new adapter that takes the output of this adapter and
* adapts to something of type NEXT.
*/
public default <NEXT> Adapter<FROM,NEXT> chain(Adapter<TO,NEXT> next){
Adapter<FROM,TO> x=this;
return new Adapter<FROM,NEXT>(){
@Override
public NEXT adapt(FROM f) {
return next.adapt(x.adapt(f));
}
};
}
我真的很喜欢 C++ 中类似的东西。到目前为止我做的最好的是:
#ifndef ADAPTER_H
#define ADAPTER_H
template <typename FROM, typename TO>
class Adapter
{
public:
TO adapt(FROM f);
template <typename NEXT> Adapter<FROM, NEXT> chain(Adapter<TO, NEXT> arg)
{
class UnNamed: public Adapter<FROM, NEXT>
{
public:
Adapter<TO, NEXT> outer;
UnNamed(Adapter<TO, NEXT> arg)
{
outer = arg;
}
NEXT adapt(FROM from)
{
outer.adapt(Adapter::adapt(from));
}
};
return UnNamed(arg);
}
};
#endif
但它因未定义的引用错误而失败。我真的很想让 api 尽可能靠近 Java,但我不知道如何让嵌套的 class 以可参考的方式存在。
你很接近。
在Java中,interface
s必须由具体的classes实现,并且interface
方法是虚拟的,必须在class中重写]是的。在您的示例中,在 chain()
方法上使用 default
关键字允许 Java 生成自己的 class 以实现 Adapter
接口chain()
returns.
在C++中,你可以做类似的事情,除了没有等价于Java的default
关键字(C++有一个default
关键字,但它的意思很不同),所以你需要显式定义你自己的 class 类型来实现你的虚拟接口。您尝试这样做,但是多态性不起作用,除非您通过 pointers/references 调用对象的虚拟方法,否则您可能会遇到 object slicing。在某种程度上,Java 也强制执行此规则,因为 Java 中的所有对象都是引用类型,只有基本类型是值类型(而在 C++ 中,classes 也可以用作值类型)。
尝试更像这样的东西:
#include <memory>
template<typename FROM, typename TO>
class Adapter
{
public:
/**
* Adapts something from one type to a type.
* @param f
* @return
*/
virtual TO adapt(FROM f) = 0;
/**
* Chains adapters.
*
* @param <NEXT>
* @param next
* @return a new adapter that takes the output of this adapter and
* adapts to something of type NEXT.
*/
template<typename NEXT>
std::unique_ptr<Adapter<FROM, NEXT>> chain(Adapter<TO, NEXT> *next)
{
class ChainedAdapter : public Adapter<FROM, NEXT>
{
private:
Adapter<FROM, TO> *x;
Adapter<TO, NEXT> *next;
public:
ChainedAdapter(Adapter<FROM, TO> *x, Adapter<TO, NEXT> *next)
: x(x), next(next) {}
NEXT adapt(FROM f) override {
return next->adapt(x->adapt(f));
}
};
return std::make_unique<ChainedAdapter>(this, next);
}
};
虽然,我想你也可以不用动态创建 chain()
return 一个新的 ChainedAdapter
,如果你对 return 非常小心的话ed 对象并且不要以任何会对其进行切片的方式使用它:
#include <memory>
template<typename FROM, typename TO>
class Adapter
{
public:
/**
* Adapts something from one type to a type.
* @param f
* @return
*/
virtual TO adapt(FROM f) = 0;
/**
* Chains adapters.
*
* @param <NEXT>
* @param next
* @return a new adapter that takes the output of this adapter and
* adapts to something of type NEXT.
*/
template<typename NEXT>
auto chain(Adapter<TO, NEXT> *next)
{
class ChainedAdapter : public Adapter<FROM, NEXT>
{
private:
Adapter<FROM, TO> *x;
Adapter<TO, NEXT> *next;
public:
ChainedAdapter(Adapter<FROM, TO> *x, Adapter<TO, NEXT> *next)
: x(x), next(next) {}
NEXT adapt(FROM f) override {
return next->adapt(x->adapt(f));
}
};
return ChainedAdapter(this, next);
}
};
我会避免直接 Java 代码翻译。
一种 C++ 方法是拥抱价值观。
template<class Sig>
struct adapter;
template<class Out, class In>
struct adapter<Out(In)>:std::function<Out(In)>
{
using std::function<Out(In)>::function;
template<class Next, class Result=std::invoke_result_t<Next const&, Out>>
adapter<Result(In)> chain( Next next ) const& {
auto tmp=*this;
return std::move(tmp).chain(std::move(next));
}
template<class Next, class Result=std::invoke_result_t<Next, Out>>
adapter<Result(In)> chain( Next next ) && {
return [self=std::move(*this), next=std::move(next)](In in)->Result {
return next(self(std::forward<In>(in)));
};
}
};
我们开始了。
adapter<int(double)> rounder=[](double d){return std::floor(d);};
adapter<double(std::istream&)> double_reader=[](auto&is){double d; is>>d; return d;};
adapter<int(std::istream&)> int_reader=double_reader.chain(rounder);
std::cout << int_reader(std::cin);
等等
这些适配器是多态值类型。您可以将它们与函数指针、lambda、std 函数或其他函数对象链接起来
上面代码中的x.adapt(foo)
调用拼写为x(foo)
。主要目的是被调用的对象...可以使用 operator()
使 then 可调用。
设计:
我让 std::function
完成了大部分繁重的工作。它是一种支持调用和可空性的多态值类型。
我们简单地继承它,转发构造函数,并添加一个 .chain
方法来检查兼容性并推导 return 类型。
我们可以扩展适配器以轻松支持多对一适配。第一步是通过在正确的位置添加一堆 ...
来支持多个输入:
template<class Sig>
struct adapter;
template<class Out, class... In>
struct adapter<Out(In...)>:std::function<Out(In...)>
{
using std::function<Out(In...)>::function;
template<class Next, class Result=std::invoke_result_t<Next const&, Out>>
adapter<Result(In...)> chain( Next next ) const& {
auto tmp=*this;
return std::move(tmp).chain(std::move(next));
}
template<class Next, class Result=std::invoke_result_t<Next, Out>>
adapter<Result(In...)> chain( Next next ) && {
return [self=std::move(*this), next=std::move(next)](In... in)->Result {
return next(self(std::forward<In>(in)...));
};
}
};
现在,first.chain(second)
语法不适用于 second
的多个输入。我们可以添加另一种方法:
template<class Sig>
struct adapter;
template<class Out, class... In>
struct adapter<Out(In...)>:std::function<Out(In...)>
{
using std::function<Out(In...)>::function;
template<class Next, class Result=std::invoke_result_t<Next const&, Out>>
adapter<Result(In...)> chain( Next next ) const& {
auto tmp=*this;
return std::move(tmp).chain(std::move(next));
}
template<class Next, class Result=std::invoke_result_t<Next, Out>>
adapter<Result(In...)> chain( Next next ) && {
return [self=std::move(*this), next=std::move(next)](In... in)->Result {
return next(self(std::forward<In>(in)...));
};
}
template<class...First>
adapter<Out(First...)> consume( adapter<In(First)>... src)&&
{
return [self=std::move(*this), ...src=std::move(src)](First... first)->Out
{
return self(src(std::forward<First>(first))...);
};
}
template<class...First>
adapter<Out(First...)> consume( adapter<In(First)>... src) const&
{
auto tmp = *this;
return std::move(tmp).consume( std::move(src)... );
}
};
但这有点远了,不是吗?
有些人对以非多态方式从值类型(如 std::function
)继承持怀疑态度。在这里,我认为几乎没有人在他们的头脑中存储指向 std::function
s 的指针;那些这样做的,通常是在做 shared_ptr
技巧(处理多态破坏)。
但是,如果您担心,可以这样做:
template<class Out, class... In>
struct adapter<Out(In...)>
{
using F = std::function<Out(In...)>;
F f;
// forward call operator
template<class...Args>
auto operator()(Args&&...args)const
-> std::invoke_result_t<F const&, Args&&...>
{
return f(std::forward<Args>(args)...);
}
// default all special member functions:
adapter()=default;
adapter(adapter const&)=default;
adapter(adapter &&)=default;
adapter& operator=(adapter const&)=default;
adapter& operator=(adapter &&)=default;
~adapter()=default;
// forward a few useful operations and ctors:
explicit operator bool() const { return (bool)f; }
template<class Fin>
requires (!std::is_same_v<Fin, adapter> && std::convertible_to<Fin, F>)
adapter( Fin fin ):f(std::forward<Fin>(fin)) {}
然后添加 .chain
方法。
如您所见,这增加了一些代码。 Live example.