尾随 return 类型、declval 和引用限定符:它们可以一起使用吗?
Trailing return type, declval and reference qualifiers: can they work together?
考虑以下示例:
#include <utility>
struct A { void f() {} };
struct B { void f() & {} };
struct C { void f() && {} };
template<typename T>
auto f() -> decltype(std::declval<T>().f())
{}
int main() {
f<A>();
// f<B>(); // (*)
f<C>();
}
当使用 B
调用时(第 (*)
行),代码不再编译,因为 std::declval
在特定情况下将 T
转换为右值引用类型。
如果我们稍微改变一下,就会出现相反的问题:
// ...
template<typename T>
auto f() -> decltype(std::declval<T&>().f())
{}
// ...
int main() {
f<A>();
f<B>();
// f<C>(); // (*)
}
现在 (*)
处的行不适用于 std::declval
在特定情况下将类型转换为左值引用类型。
有没有办法定义一个接受类型 T
的表达式,如果它有一个成员函数 f
,不管它的引用限定符是什么?
我没有任何真实的案例可以使用它,我也无法举出任何真实的使用示例。
这个问题是为了好奇,仅此而已。
我知道 ref-qualifier 存在是有原因的,我不应该试图破坏 class 的设计。
您可以使用 Boost.Hana 的 overload_linearly
来测试两种变体:
template<typename T>
using lval_of = add_lvalue_reference_t<decay_t<T>>;
auto call_f = hana::overload_linearly(
[](auto&& t) -> decltype(move(t).f()){},
[](auto&& t) -> decltype(declval<lval_of<decltype(t)>>().f()){}
);
template<typename T>
auto f() -> decltype(call_f(declval<T>()))
{}
构建一个类型特征,如果表达式 declval<T>().f(declval<Args>()...)
是一个有效调用,则 returns 为真。然后传入 U&
和 U&&
表示 T
.
类型的左值或右值对象
namespace detail{
template<class...>struct voider{using type=void;};
template<class... Ts>using void_t=typename voider<Ts...>::type;
template<template<class...> class, class=void, class...>
struct can_apply : false_type { };
template<template<class...> class L, class... Args>
struct can_apply<L, void_t<L<Args...>>, Args...> : true_type {};
template<class T>
using rvalue = decltype(declval<T>().f());
template<class T>
using lvalue = decltype(declval<T&>().f());
template<class T>
using can_apply_f
= integral_constant<bool, detail::can_apply<rvalue, void_t<>, T>{} ||
detail::can_apply<lvalue, void_t<>, T>{}>;
}
template<class T>
enable_if_t<detail::can_apply_f<T>{}>
f();
在 C++17 中,这变得更简单了:
namespace detail{
auto apply=[](auto&&g,auto&&...xs)->decltype(decltype(g)(g).f(decltype(xs)(xs)...),void()){};
template<class T>
using ApplyF = decltype(apply)(T);
template<class T>
using can_apply_f = std::disjunction<std::is_callable<ApplyF<T&>>, std::is_callable<ApplyF<T&&>>>;
}
考虑以下示例:
#include <utility>
struct A { void f() {} };
struct B { void f() & {} };
struct C { void f() && {} };
template<typename T>
auto f() -> decltype(std::declval<T>().f())
{}
int main() {
f<A>();
// f<B>(); // (*)
f<C>();
}
当使用 B
调用时(第 (*)
行),代码不再编译,因为 std::declval
在特定情况下将 T
转换为右值引用类型。
如果我们稍微改变一下,就会出现相反的问题:
// ...
template<typename T>
auto f() -> decltype(std::declval<T&>().f())
{}
// ...
int main() {
f<A>();
f<B>();
// f<C>(); // (*)
}
现在 (*)
处的行不适用于 std::declval
在特定情况下将类型转换为左值引用类型。
有没有办法定义一个接受类型 T
的表达式,如果它有一个成员函数 f
,不管它的引用限定符是什么?
我没有任何真实的案例可以使用它,我也无法举出任何真实的使用示例。
这个问题是为了好奇,仅此而已。
我知道 ref-qualifier 存在是有原因的,我不应该试图破坏 class 的设计。
您可以使用 Boost.Hana 的 overload_linearly
来测试两种变体:
template<typename T>
using lval_of = add_lvalue_reference_t<decay_t<T>>;
auto call_f = hana::overload_linearly(
[](auto&& t) -> decltype(move(t).f()){},
[](auto&& t) -> decltype(declval<lval_of<decltype(t)>>().f()){}
);
template<typename T>
auto f() -> decltype(call_f(declval<T>()))
{}
构建一个类型特征,如果表达式 declval<T>().f(declval<Args>()...)
是一个有效调用,则 returns 为真。然后传入 U&
和 U&&
表示 T
.
namespace detail{
template<class...>struct voider{using type=void;};
template<class... Ts>using void_t=typename voider<Ts...>::type;
template<template<class...> class, class=void, class...>
struct can_apply : false_type { };
template<template<class...> class L, class... Args>
struct can_apply<L, void_t<L<Args...>>, Args...> : true_type {};
template<class T>
using rvalue = decltype(declval<T>().f());
template<class T>
using lvalue = decltype(declval<T&>().f());
template<class T>
using can_apply_f
= integral_constant<bool, detail::can_apply<rvalue, void_t<>, T>{} ||
detail::can_apply<lvalue, void_t<>, T>{}>;
}
template<class T>
enable_if_t<detail::can_apply_f<T>{}>
f();
在 C++17 中,这变得更简单了:
namespace detail{
auto apply=[](auto&&g,auto&&...xs)->decltype(decltype(g)(g).f(decltype(xs)(xs)...),void()){};
template<class T>
using ApplyF = decltype(apply)(T);
template<class T>
using can_apply_f = std::disjunction<std::is_callable<ApplyF<T&>>, std::is_callable<ApplyF<T&&>>>;
}