c++ - 一个“constexpr”标记的变量和一个静态存储持续时间的变量是否可以通过带有推导指南的类类型进行存储?
问题描述
考虑以下代码:
template <typename T>
struct wrap {
T thing;
constexpr wrap(T thing) : thing(thing) {}
};
template <typename T>
wrap(const T&) -> wrap<T>;
template <wrap V>
void fun();
struct X {
int a;
};
int main() {
constexpr auto a1 = &X::a;
static const auto a2 = &X::a;
fun<a1>();
fun<a2>(); // Doesn't compile
}
现在的问题是,无需显式声明模板参数即可成功通过a1
,但没有静态存储持续时间wrap
a2
。
如果我将上述扣除指南更改为:
template <typename T>
wrap(const T&) -> wrap<const T&>; // <-- Note the 'const T&' here instead of plain 'T'
如果可能的话,如何修改上面的代码,以便能够同时传递a1
和a2
分别传递,而不必显式声明像fun<wrap<decltype(a1)>{a1}>()
or之类的类型fun<wrap<const decltype(a2)&>{a2}>()
?
解决方案
当然,这是可能的。但是,在我解释解决方案之前,请允许我建议您通过坚持使用特定界面(在我看来)实际上并不比替代方案更干净,从而使问题变得不必要地困难。从本质上讲,您要求fun<arg>
按arg
价值或按参考取值,具体取决于哪一个实际上格式正确。在 的情况下a1
,只能按值取值;它不能被引用,因为它没有静态存储持续时间。在 的情况下a2
,由于未声明,因此无法按值获取constexpr
,但可以通过引用获取,因为它具有静态存储持续时间。
使用您提议的版本的代码fun
很难阅读,因为读者看到fun<arg>
后,不会立即知道arg
是按值还是按引用。读者必须根据读者自己的知识来推断它是哪一个,是通过值还是通过引用arg
来允许的非类型模板参数。此外,一些参数可能符合任一条件,在这种情况下,读者还必须知道实现者为这种情况选择了哪个默认值,才能知道发生了什么。fun
同样,这只是我的观点:如果你编写单独的函数,可能会更简单,也许调用它们fun_val
and fun_ref
, where fun_val<a1>()
andfun_ref<a2>()
是格式良好的。为此,我们应该定义两个包装类,一个通过值获取参数,另一个通过引用:
template <typename T>
struct wrap_value {
using value_type = T;
T thing;
constexpr wrap_value(T thing) : thing(thing) {}
};
template <typename T>
wrap_value(const T&) -> wrap_value<T>;
template <typename T>
struct wrap_reference {
using value_type = T;
const T& thing;
constexpr wrap_reference(const T& thing) : thing(thing) {}
};
template <typename T>
wrap_reference(const T&) -> wrap_reference<T>;
template <wrap_value V>
void fun_val() {
std::cout << "value\n";
}
template <wrap_reference V>
void fun_ref() {
std::cout << "reference\n";
}
struct X {
int a;
};
int main() {
constexpr auto a1 = &X::a;
static const auto a2 = &X::a;
static const int x = 42;
fun_val<a1>(); // OK
fun_ref<a1>(); // Error
fun_val<a2>(); // Error
fun_ref<a2>(); // OK
fun_val<x>(); // OK; uses value of x
fun_ref<x>(); // OK; uses address of x
}
现在,如果您坚持使用单个 name fun
,那么关键是要识别它a1
并a2
具有相同的类型,因此 CTAD 的单个应用程序将永远无法找出正确的包装器类型以使调用格式正确。相反,您必须将 SFINAE 与两个重载一起使用:对于给定模板参数无效的一个(因为它按值(resp.reference)获取参数,而不能按值(resp.reference)获取)被丢弃。基本上,将上述示例中的fun_val
和都重命名为:fun_ref
fun
template <wrap_value V>
void fun() {
std::cout << "value\n";
}
template <wrap_reference V>
void fun() {
std::cout << "reference\n";
}
a1
这在and的情况下工作得很好a2
,只有两个重载之一是候选者。但在 的情况下x
,它将是模棱两可的。假设您想在这种情况下强制选择按值重载。我们可以通过插入一个使按引用重载不是候选对象的约束来做到这一点:
template <wrap_reference V> requires(!requires { fun<wrap_value<typename decltype(V)::value_type>(V.thing)>(); })
void fun() {
std::cout << "reference\n";
}
您可以在此处查看完整的工作示例。
推荐阅读
- visual-studio-code - 如何在 VSCode 中运行 Spark 批处理作业
- android-studio - 使用 OnClickListener 的第二个活动中的详细信息
- assembly - 是否可以在同一条指令中使用相同的寄存器作为内存指针和数据?
- embedded - 微控制器中的定时器/预分频器
- redirect - 如何解决 IIS 管理器中的 403 或重定向错误
- javascript - 通过 $Emit 更新子组件和父组件数据
- python - 在列表python中查找最小和最大元素的最后一个索引
- android - FMX [Android] 使多个标签加粗
- python - 在python中循环可选参数(字符串)
- ansible - 使用 Ansible 从开始值和结束值生成 IP 地址列表