【问题标题】:Is there a trick that let's one "pass a namespace" into a template?有没有一个技巧可以让我们将命名空间“传递给模板”?
【发布时间】:2023-03-14 04:19:01
【问题描述】:

情景:

假设我们有几个库,每个库都在自己的命名空间中,每个库都包含完全相同的类和函数集,并且在每个命名空间中都具有相同的 API。这些库完全不相互了解,也没有共同的基础。

namespace A {
class Foo { ... };
class Bar { ... };
class Baz { ... };
// A dozen more types
};

namespace B {
class Foo { ... };
class Bar { ... };
class Baz { ... };
// A dozen more types
};

namespace C {
class Foo { ... };
class Bar { ... };
class Baz { ... };
// A dozen more types
};

我绝不为这种设计辩护,但让我们将其视为无法更改的约束。显然有更好的方法可以做到这一点,但我现在无权改变它。

问题:

鉴于上述情况,我们通常希望实现使用这些库的更高级别的代码,但我们不想为每个库都重复此代码。模板似乎是显而易见的解决方案。

template<typename Foo, typename Bar, typename Baz>
class FooBarBazzer
{
    std::unique_ptr<Foo> foo;
    std::vector<Bar> bars;
    Baz baz;
    ...
}

这看起来还不错,但它要求我们将FooBarBaz 列为模板参数,并且随着涉及的类型数量的增加,这很快导致了一些非常笨拙的模板参数列表。

我发现自己想要以下虚构的语法:

template<namespace ns>
class FooBarBazzer
{
    std::unique_ptr<ns::Foo> foo;
    std::vector<ns::Bar> bars;
    ns::Baz baz;
    ...
}

一种可能的解决方法是为每个命名空间编写和维护一个特征struct,其中包含该命名空间中每个类型的成员类型。

namespace A {
struct Types {
    using Foo = A::Foo;
    using Bar = A::Bar;
    using Baz = A::Baz;
    // A dozen more type aliases
};
}

namespace B {
struct Types {
    using Foo = B::Foo;
    using Bar = B::Bar;
    using Baz = B::Baz;
    // A dozen more type aliases
};
}

namespace C {
struct Types {
    using Foo = C::Foo;
    using Bar = C::Bar;
    using Baz = C::Baz;
    // A dozen more type aliases
};
}

template<typename Types>
class FooBarBazzer
{
    std::unique_ptr<typename Types::Foo> foo;
    std::vector<typename Types::Bar> bars;
    typename Types::Baz baz;
    ...
}

但这似乎有很多样板。 (还有很多 typename 关键字散布在周围,这并不理想,但我怀疑这是可以避免的。)

问题:

有没有更好的技巧来做到这一点?在实例化时告诉模板应该在哪个命名空间中查找类型?一个不需要编写和维护太多样板的?

我怀疑答案是否定的,但我一直对专家能够让 C++ 类型系统做的事情感到惊讶。

【问题讨论】:

  • 根据您对 DoAllFoosBar 的实际 impl 的外观,我们或许可以避免使用参数包而不是显式列出所有类型
  • 写一些类型特征可能会有所帮助。
  • 不,你不能这样做。模板适用于类型,而不是命名空间。如果你想要一个“上帝”类型,你必须定义一个服务于这个目的的类型(你自己提到这个)。你也可以使用宏,如果你愿意的话。
  • 简短的回答是不,没有办法将命名空间传递给模板。但是,正如您在问题中所承认的那样,这似乎非常适合仅利用 ADL。您是否有任何理由对仅使用 SFINAE 约束 DoAllFoosBar 而不是尝试约束到整个命名空间感到不满?

标签: c++ templates namespaces


【解决方案1】:

简答

没有办法将namespace 传递给template;但这并不意味着您的情况没有其他选择。

长答案

虽然没有办法专门为此传递namespace,但也没有真正的理由不能以通用方式支持您的用例。您对使用特征的建议肯定会奏效——但根据您的问题的具体情况,可能是不必要的手动操作。

您提到了您对类型数量增长的担忧——这可以通过可变参数列表来满足:

template<typename Foo, typename Bar, typename...Args>
bool DoAllFoosBar(const std::vector<Foo> & foos, const Bar & bar, const Args&...args)
{
    return std::all_of(foos.begin(),
                       foos.end(),
                       [&](const auto & foo){ return IsFooBar(foo, bar, args...); });
}

只要参数中的 1 个明确匹配来自 namespace 的内容(从它的声音来看,它应该),它仍然会通过 ADL 限定调用——它现在支持两个 或更多 论据。

此外,尽管您不能严格限制 namespace 本身 - 您可以将函数限制为仅在 IsFooBar 可在其参数上调用时才可调用 - 通过 SFINAE 或仅通过 @987654327 @。

例如:

// Dummy definition in current scope, so it can find others through ADL
void IsFooBar();

template<typename Foo, typename Bar, typename...Args>
auto DoAllFoosBar(const std::vector<Foo> & foos, const Bar & bar, const Args&...args)
  -> decltype(IsFooBar(foos, bar, args...))
  // ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- expression sfinae
{
  ...
}

当且仅当IsFooBar(...) 产生有效结果时,这使用表达式 SFINAE 有条件地启用 DoAllFoosBar。由于它还在当前范围内看到IsFooBar,因此它还将通过 ADL 查找所有有效条目。你也可以做类似的事情来把它变成static_assert

【讨论】:

  • 在这种情况下,我关心的其他类型根本不是函数参数,而是较大对象的命名成员。所以可变参数模板根本没有帮助。不过谢谢你的建议。
  • 如果是这种情况,那么您应该将其添加到问题中。对于缺乏所有细节的问题,不可能提供答案。但是根据您刚才的描述,可以使用指向成员的指针来实现相同的目的,以便根据需要提取所需的成员。
  • 我已经更新了我的示例,让它变得更复杂一些,并展示了为什么可变参数模板参数是不够的。我之前不清楚。这些类型在泛型代码中用作成员对象,因此它不是指向成员的指针可以解决的问题。
猜你喜欢
  • 2013-06-11
  • 2022-09-24
  • 1970-01-01
  • 2022-06-12
  • 2015-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-18
相关资源
最近更新 更多