(我最感兴趣的是 c++17 解决方案,即使你看到我使用
std::unwrap_ref_decay
;想象一下我已经在我的 C++17 代码中复制并粘贴了可能的实现。另一方面,我也很好奇你会如何在 c++20 中解决这个问题。)
假设我有一个自定义点对象,
cpo
,定义如下,
constexpr struct CPO {
template<typename T>
constexpr auto operator()(T&& t) const -> decltype(tag_invoke(*this, std::forward<T>(t))) {
return tag_invoke(*this, std::forward<T>(t));
}
} cpo{};
还有像这样的模板类
template<std::size_t N>
struct Foo {
constexpr int cpo() {
return 1;
};
};
非常重要的细节:在我的实际用例中,
.cpo
实际上返回对象内部的引用,有点像operator[]
对std::array
所做的那样。
Foo
的所有实例化都可以实现自定义点的一种方式是这样的
template<std::size_t N>
constexpr auto tag_invoke(CPO, Foo<N> foo) {
return foo.cpo();
}
但是,如果我希望能够将
std::reference_wrapper<Foo<N>>
传递给 cpo
呢?毕竟,通过 std::ref(foo)
/std::cref(foo)
构造的对象,其中 foo
是 Foo<N>
应该表现得像a Foo<N>&
,所以我希望 cpo
的 Foo<N>
的定制也接受包裹在 Foo<N>
中的 std::reference_wrapper
。
然而,果实似乎并没有挂得那么低。
如果
cpo
是针对 Foo
的一个(或多个)特定实例实现的,
constexpr auto tag_invoke(CPO, Foo<2> foo) {
return foo.cpo();
}
然后传递一个
std::reference_wrapper<Foo<2>>
就可以了,因为它可以转换为具体类型 Foo<2>
int main() {
Foo<2> foo;
cpo(foo);
cpo(std::move(foo));
auto r = std::ref(foo);
cpo(r);
cpo(std::move(r));
auto cr = std::cref(foo);
cpo(cr);
cpo(std::move(cr));
}
但是,一旦我概括了实现(例如,我回到在
tag_invoke
上模板化的 N
),当 main
传递了引用包装器参数时,上面的 cpo
就无法编译。
我解决这个问题的尝试包括定义一个
is_foolike_v
元谓词,并在 SFINAE 的 tag_invoke
的定义中使用它——仅在那些 Foo<N>
或 std::reference_wrapper<Foo<N>>
的类型中:
template <typename T, std::enable_if_t<is_foolike_v<T>, bool> = true>
constexpr auto tag_invoke(CPO, T&& foolike) {
return foolike.cpo();
}
有了这个,实例化似乎工作正常,但是
foolike.cpo()
变成了硬编译时失败,因为我无法在 .cpo()
上调用 std::reference_wrapper
,即使它包装了 Foo<N>
, .cpo()
是可能的。
进一步尝试手动打开包装,
template <typename T, std::enable_if_t<is_foolike_v<T>, bool> = true>
constexpr decltype(auto) tag_invoke(CPO, T&& foolike) {
return std::decay_t<std::unwrap_ref_decay_t<T>>(std::forward<T>(foolike)).cpo();
}
但是除了我还没有检查这在完美转发方面是否表现良好之外,它仍然部分硬编码了foolike
可能是
std::reference_wrapper
这一事实的知识。是的,在这种情况下并没有真正的 if constexpr
,因为它是通过 std::unwrap_ref_decay
处理的,对于非 std::reference_wrapper
类型来说这是一个禁止操作...但我仍然想知道我是否把事情复杂化了。
完整示例(1) 不,我不想显式实现 std::reference_wrapper
的自定义点,因为为
Foo
定义了许多不同的 CPO,有些是二进制的,有些是三元的,与上面定义的一元 cpo
不同,因此这将迫使我为std::reference_wrapper
/非std::reference_wrapper
的所有组合实施每个 CPO。enable_if
,消除所有
is_foolike
。template <typename T, std::enable_if_t<is_foo_v<std::decay_t<std::unwrap_ref_decay_t<T>>>, bool> = true>
您可能还想以未包装类型转发,而不是转发到副本。
return std::forward<std::unwrap_ref_decay_t<T>>(foolike).cpo();
注:这将导致
cpo(cref(foo))
失败,因为
Foo::cpo
不是 const 限定的。