能够采用第三方依赖的最佳扩展机制?

Best extension mechanism to be able to adopt third-party dependencies?

我目前正尝试着手研究概念。让我们假设我有一个概念:

template <class T> concept Initable = requires (T&t) { { init(t) }; };
  // just for demonstration purposes, real concept makes more sense ...

并且我想为像std::optional这样的第三方class提供一个采用层来实现这个概念,对我来说最无缝的方式是什么?

显然,下面这段代码失败了:

template <std::semiregular T>
T& init(std::optional<T> &v) { /* contents not that important */ v = T{}; return *v; }

static_assert(Initable<std::optional<int>>, "Oh no!"); // Fails!

原因是二阶段查找。

在第 1 阶段尝试解决 Initable 概念中的 init 时,我对 init 的定义不可用,因为它在 下方提供 Initable 概念。

在第 2 阶段尝试解决它时,通过参数相关查找找不到我的定义,因为它没有在 std 命名空间中提供。

因此,两个明显的解决方案是在定义 Initable 概念之前提供 init 的定义,或者将 init 移动到 std 命名空间。

但我想在没有

的情况下为 std::optional 实施该概念

最好的方法是什么?在定义 Initable 概念时,我可以让它更容易实现吗?

基本上我在问,this可能吗?

#include <concepts>
#include <optional>

template <class T>
concept Initable =
    requires (T& t) { init(t); } // May be changed to make the task easier.
;

// Insert code *here* that magically makes std::optional implement Initable.

static_assert(Initable<std::optional<int>>, "std::optional is not initable!");

如果不是,下一个最好的事情是什么?

解析为模板实例化期间可见的任何函数显然违反了两阶段查找的原则。我决定改用不同的方法并使用在单独的自定义命名空间中定义的虚拟参数:

namespace CP { // CP = CustomizationPoint
    struct Token {};
}

现在可以在 CP 命名空间或命名空间中定义 init 函数,对象定义在:

namespace std {
    template <class T> void init(std::optional<T> &v, CP::Token) { ... }
    // illegal, but works -- do not declare stuff in namespace std.
}

namespace CP {
    template <class T> void init(std::optional<T> &v, CP::Token) { ... }
}

这已经很不错了。如果 std 命名空间中的定义不需要 CP::Token 参数,那就更好了。这可以通过提供一个函数对象来完成,该函数对象将解析为正确的函数(类似于引入标准库的自定义点对象):

constexpr inline struct {
    template <class T>
    auto operator()(std::optional<T>& v, CP::Token t) const
        -> decltype(init(v, t), []{}())
    {
        init(v, t);
    }

    template <class T>
    auto operator()(std::optional<T>& v, ...) const
        -> decltype(init(v), []{}())
    {
        init(v)
    }
} init;

这个函数对象有点笨重,但它将解析为带有 CP::Token 参数的变体(如果可用),否则返回到没有令牌参数的变体(如果可用) .

对我来说,这似乎是一种非常明智的方法,可以完美扩展,甚至允许我们覆盖已经具有正确名称但语义错误的实现。

必须稍微修改这个概念才能起作用:

template <class T> concept Initable = requires (T&t) { init(t, CP::Token{}); };