如何使tag_invoke CPO统一处理与元谓词/概念匹配的类型以及包装在reference_wrapper中的类型?

问题描述 投票:0回答:1

(我最感兴趣的是 解决方案,即使你看到我使用

std::unwrap_ref_decay
;想象一下我已经在我的 C++17 代码中复制并粘贴了可能的实现。另一方面,我也很好奇你会如何在 中解决这个问题。)

假设我有一个自定义点对象,

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。
    

c++ c++17 template-meta-programming reference-wrapper tag-invoke
1个回答
0
投票
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 限定的。
    

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.