这是与Using std::forward on sub fields类似的问题,但答案似乎并不适用于我的情况。
template<class Base, class F>
void visit(Base&&, const F&) {
throw std::bad_cast();
}
template<class Derived, class... Rest, class Base, class F>
void visit(Base&& base, const F& f) {
if (auto *as_derived = dynamic_cast<Derived *>(&base)) {
return f(std::forward<Base>(*as_derived));
} else {
return visit<Rest...>(std::forward<Base>(base), f);
}
}
我的目标是以下测试用例:
struct Animal {
virtual ~Animal() {}
};
struct Cat : Animal {
void speak() & { puts("meow"); }
void yowl() && { puts("MEOW!"); }
};
struct Dog : Animal {
void speak() & { puts("woof"); }
void yowl() && { puts("WOOF!"); }
};
int main() {
Animal *a = new Cat();
Animal *b = new Dog();
visit<Cat, Dog>(*a, [](auto&& a){ std::forward<decltype(a)>(a).speak(); });
visit<Cat, Dog>(*b, [](auto&& a){ std::forward<decltype(a)>(a).speak(); });
visit<Cat, Dog>(std::move(*a), [](auto&& a){ std::forward<decltype(a)>(a).yowl(); });
visit<Cat, Dog>(std::move(*b), [](auto&& a){ std::forward<decltype(a)>(a).yowl(); });
}
期望的输出:“喵”“woof”“MEOW!” “纬!”。请注意,函数speak
和yowl
是非虚拟的;这在我的原始代码中是必需的,因为它们实际上是模板,模板不能是虚拟的。
此处编写的此代码的问题是std::forward<Base>(*as_derived)
不仅仅更改*as_derived
上的ref-qualifiers和const-qualifiers以实现完美转发;它实际上将类型强制转换为Base&
,使visit
的整个点变得紧张!
是否有一个标准库函数可以执行我想要 std::forward
要做的事情 - 即更改*as_derived
上的ref-qualifiers和const-qualifiers以匹配这将是从std::forward<Base>
如果没有标准库函数,我怎么能写一个“完美转发子类型”函数供我自己使用?
上面的Wandbox链接包含一些“适用于此测试用例”的内容,但它不保留常量,并且它看起来并不优雅。
答案 0 :(得分:4)
标准中没有任何内容。但是写起来并不难。只是烦人你需要做的是编写一个特性,为你提供传递给forward
的类型 - 基本上你想要匹配Derived
的cv资格和参考资料到Base
是什么,然后将该类型传递到forward
:
return f(std::forward<match_ref_t<Base, Derived>>(*as_derived));
一个简单的实现,几乎可以肯定会更简洁,只是:
template <class From, class To>
struct match_ref {
using type = To;
};
template <class From, class To>
using match_ref_t = typename match_ref<From, To>::type;
template <class From, class To>
struct match_ref<From&, To> {
using type = match_ref_t<From, To>&;
};
template <class From, class To>
struct match_ref<From&&, To> {
using type = match_ref_t<From, To>&&;
};
template <class From, class To>
struct match_ref<From const, To> {
using type = match_ref_t<From, To> const;
};
template <class From, class To>
struct match_ref<From volatile, To> {
using type = match_ref_t<From, To> volatile;
};
template <class From, class To>
struct match_ref<From const volatile, To> {
using type = match_ref_t<From, To> const volatile;
};
或者,我想:
template <class Check, template <class> class F, class T>
using maybe_apply = std::conditional_t<Check::value, F<T>, T>;
template <class From, class To>
struct match_ref {
using non_ref = std::remove_reference_t<From>;
using to_cv = maybe_apply<std::is_const<non_ref>, std::add_const_t,
maybe_apply<std::is_volatile<non_ref>, std::add_volatile_t,
To>>;
using type = std::conditional_t<
std::is_lvalue_reference<From>::value,
to_cv&,
std::conditional_t<
std::is_rvalue_reference<From>::value,
to_cv&&,
to_cv>
>;
};
template <class From, class To>
using match_ref_t = typename match_ref<From, To>::type;
答案 1 :(得分:2)
前进只是一个有条件的举动。
template<bool b>
struct move_if_t{
template<class T>
T&& operator()(T&t)const{ return std::move(t); }
};
template<>
struct move_if_t<false>{
template<class T>
T& operator()(T&t)const{ return t; }
};
template<bool b, class T>
decltype(auto) move_if(T& t){
return move_if_t<b>{}(t);
}
现在我们得到
template<class Derived, class... Rest, class Base, class F>
void visit(Base&& base, const F& f) {
if (auto *as_derived = dynamic_cast<Derived *>(&base)) {
return f(move_if<!std::is_lvalue_reference<Base>{}>(*as_derived));
} else {
return visit<Rest...>(std::forward<Base>(base), f);
}
}
答案 2 :(得分:0)
Yakk的回答非常简洁,似乎有效,但我最终还是接受了Barry在实践中的回答,因为我发现match_cvref_t
比任何替代方案更容易推理。另外,在我的特定情况下,我最终还是需要引用match_cvref_t
来正确地进行实际的转换操作。因此:
template<class Base, class F>
void visit(Base&&, const F&) {
throw std::bad_cast();
}
template<class DerivedClass, class... Rest, class Base, class F>
void visit(Base&& base, const F& f) {
if (typeid(base) == typeid(DerivedClass)) {
using Derived = match_cvref_t<Base, DerivedClass>;
return f(std::forward<Derived>(static_cast<Derived&&>(base)));
} else {
return visit<Rest...>(std::forward<Base>(base), f);
}
}
我确实设法将match_cvref_t
缩小为
template<class From, class To>
using match_cvref_t = match_ref_t<
From,
match_cv_t<
std::remove_reference_t<From>,
std::remove_reference_t<To>
>
>;
其中match_cv_t
和match_ref_t
各占约5行代码。