【问题标题】:Best extension mechanism to be able to adopt third-party dependencies?能够采用第三方依赖项的最佳扩展机制?
【发布时间】:2021-04-23 14:55:00
【问题描述】:

我目前正试图让我的脚湿透概念。让我们假设我有一个概念:

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

我想为像std::optional 这样的第三方类提供一个采用层来实现这个概念,对我来说最简单的方法是什么?

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

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 概念下方提供。 p>

在第 2 阶段尝试解决它时,我的定义无法通过依赖于参数的查找找到,因为它未在 std 命名空间中提供。

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

但我想在没有的情况下为std::optional 实现这个概念

  • 依赖于特定的定义/包含顺序,
  • 填充std 命名空间和
  • 在调用方站点使用过多样板代码。

最好的方法是什么?在定义 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!");

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

【问题讨论】:

  • 我不明白你在做什么。您的架构中似乎有太多您不清楚的组件:您实现了什么(可能是一个库?)、用户(您的代码的消费者)和第 3 方库。你定义了一个概念,但这个概念“可以由用户实现”是什么意思?要么你实现这个概念,要么你的用户实现这些概念。对我个人来说没有意义。 “将它们与我假设的图书馆一起使用。”除非您拥有并重写该库,否则您无法将概念添加到现有库中。
  • @bolov:我的库:定义概念,第三方库:提供未实现该概念的类,用户代码:在第三方类之上实现概念,然后想将该类用作如果它实现了这个概念。当这样说时,它归结为扩展第三方类的公共接口。现在我意识到我可能要求的太多了。
  • 要么我误解了你,要么你误解了概念。一个类不会“声明”实现一个概念。一个类要么满足一个概念,要么不满足。如果你定义一个概念(例如默认可初始化),一个类将满足这个概念,如果它是默认可初始化的。
  • 你的sn-ps代码不足以理解你的问题。你能创建一个小的minimal reproducible example吗?
  • @bolov:我试图重新表述这个问题,希望现在更清楚?

标签: c++ argument-dependent-lookup


【解决方案1】:

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

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 参数的变体,否则回退到不带 token 参数的变体(如果可用)。

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-09
    • 1970-01-01
    • 2019-05-30
    相关资源
    最近更新 更多