You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让tag_invoke CPO统一处理匹配元谓词的类型与reference_wrapper包装类型?

C++17/20下CPO兼容std::reference_wrapper的通用解决方案

需求背景

核心需求是让自定义的定制点对象(CPO)既能处理Foo<N>类型,也能无缝兼容std::reference_wrapper<Foo<N>>,同时避免为每个CPO单独编写reference_wrapper的重载实现(减少重复代码)。优先关注C++17解决方案(可自行实现std::unwrap_ref_decay逻辑),同时了解C++20的简化写法。

现有代码与问题

1. 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{};

2. 模板类Foo

template<std::size_t N>
struct Foo {
    constexpr int& cpo() { return i; }
    constexpr int const& cpo() const { return i; }
    int i;
};

3. 初始tag_invoke实现(存在缺陷)

最初针对Foo<N>的实现不仅有拷贝问题,还无法兼容std::reference_wrapper<Foo<N>>:

template<std::size_t N>
constexpr auto tag_invoke(CPO, Foo<N> foo) {
    return foo.cpo();
}
  • 传入std::ref(foo)时编译失败,因为没有匹配的tag_invoke重载;
  • 即便单独针对特定Foo<N>实例实现兼容逻辑,泛化到所有N时依然无法自动转换。

4. 当前解决方案的局限

通过自定义is_foolike_v元谓词筛选类型,结合std::unwrap_ref_decay处理的方案虽能运行,但存在硬编码reference_wrapper逻辑的问题;在类似std::array提取首元素的场景中,赋值操作(如cpo(arr)=3)会失败,需用if constexpr区分类型,实现繁琐:

template <typename T, std::enable_if_t<is_foolike_v<T>, bool> = true>
constexpr decltype(auto) tag_invoke(CPO, T&& foolike) {
    return std::unwrap_ref_decay_t<T>(std::forward<T>(foolike)).cpo();
}

C++17优化方案

核心思路是:先实现通用的解引用工具,再通过一层转发重载,将所有可解包为Foo<N>的类型(包括reference_wrapper)转发给针对原始Foo<N>的完美转发实现,避免重复代码。

1. 实现C++17版unwrap_ref_decay

标准库C++17未提供该工具,自行实现:

#include <type_traits>
#include <functional>

template<typename T>
constexpr auto unwrap_ref_decay(T&& t) noexcept {
    using U = std::decay_t<T>;
    if constexpr (std::is_same_v<U, std::reference_wrapper<std::remove_reference_t<T>>>) {
        return t.get();
    } else {
        return std::forward<T>(t);
    }
}

2. 针对Foo<N>的完美转发tag_invoke

先解决拷贝问题,为Foo<N>的所有值类别实现重载:

template<std::size_t N>
constexpr decltype(auto) tag_invoke(CPO, Foo<N>& foo) noexcept {
    return foo.cpo();
}

template<std::size_t N>
constexpr decltype(auto) tag_invoke(CPO, const Foo<N>& foo) noexcept {
    return foo.cpo();
}

template<std::size_t N>
constexpr decltype(auto) tag_invoke(CPO, Foo<N>&& foo) noexcept {
    return std::move(foo).cpo();
}

template<std::size_t N>
constexpr decltype(auto) tag_invoke(CPO, const Foo<N>&& foo) noexcept {
    return std::move(foo).cpo();
}

3. 通用转发重载(兼容所有可解包为Foo<N>的类型)

首先定义元谓词判断类型是否可解包为Foo<N>:

template<typename T>
struct is_foo : std::false_type {};

template<std::size_t N>
struct is_foo<Foo<N>> : std::true_type {};

template<typename T>
constexpr bool is_foo_v = is_foo<T>::value;

template<typename T>
constexpr bool is_unwrappable_to_foo_v = 
    is_foo_v<std::decay_t<decltype(unwrap_ref_decay(std::declval<T>()))>>;

然后编写通用转发的tag_invoke重载:

template<typename T>
constexpr std::enable_if_t<is_unwrappable_to_foo_v<T>, decltype(auto)>
tag_invoke(CPO, T&& t) noexcept {
    auto&& unwrapped = unwrap_ref_decay(std::forward<T>(t));
    return tag_invoke(cpo, std::forward<decltype(unwrapped)>(unwrapped));
}

该重载会自动处理std::reference_wrapper<Foo<N>>,同时完美转发值类别,支持赋值操作(如cpo(std::ref(foo)) = 5)。


C++20简化方案

C++20标准库提供了std::unwrap_ref_decay,同时可通过Concept简化元谓词,代码更简洁。

1. 定义FooLike Concept

#include <type_traits>
#include <functional>

template<typename T>
struct is_foo : std::false_type {};

template<std::size_t N>
struct is_foo<Foo<N>> : std::true_type {};

template<typename T>
concept FooLike = is_foo<std::unwrap_ref_decay_t<T>>::value;

2. 简化的通用tag_invoke重载

template<FooLike T>
constexpr decltype(auto) tag_invoke(CPO, T&& t) noexcept {
    auto&& unwrapped = std::unwrap_ref_decay(std::forward<T>(t));
    return tag_invoke(cpo, std::forward<decltype(unwrapped)>(unwrapped));
}

3. C++20风格的CPO定义(可选)

用requires表达式替代decltype,写法更直观:

constexpr struct CPO {
    template<typename T>
    constexpr decltype(auto) operator()(T&& t) const
        requires requires { tag_invoke(*this, std::forward<T>(t)); }
    {
        return tag_invoke(*this, std::forward<T>(t));
    }
} cpo{};

内容的提问来源于stack exchange,提问作者Enlico

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 01:45:55