【问题标题】:Perfect forwarding without ODR-use无需使用 ODR 即可完美转发
【发布时间】:2017-03-19 18:38:00
【问题描述】:

考虑下面的sn-p,好像写在头文件里:

struct Foo {
    // ...
};

template <class... Args>
Foo makeFoo(Args &&... args) {
    return {std::forward<Args>(args)...};
}

我可以用一些参数调用makeFoo,然后取回Foo。太好了。

现在我想做的是用标签替换makeFoo的一些参数,看起来就像这样(仍然在头文件中):

constexpr struct tag_type { using type = ActualType; } tag;

应该在makeFoo 中检测到这些标签,并在调用Foo 的构造函数之前替换实际对象。所以调用看起来像:

auto myFoo = makeFoo("hi", ::tagBar, 42, ::tagBaz);

但问题是:这种声明标签的方式非常方便,但如果我使用 ODR 中的任何一个,我需要在其他地方提供定义。一点都不方便。

根据this conveniently specific answer(强调我的):

“该对象没有被 odr-used” 可能是唯一有问题的条件。基本上,它要求您不必将变量运行时作为符号存在,这反过来意味着

  • 你没有将它绑定到一个参考(=>你没有转发它!)
  • [...]

...我确实在泡菜。

如何在不使用 ODR 的情况下从参数中筛选出标签,同时完美转发其他参数?

  • “标签”被定义为具有type typedef 的东西。

  • 每个标记声明都由宏 defineTag(name, ActualType) 生成,因此只要它是独立的并且不会(过多)改变对 makeFoo 的调用的语法,就可以更改它。

  • 完全不关心 ODR 的替代解决方案也可以。

  • C++17 的内联变量听起来像是救命稻草,但我想避免因为这个单一问题而将自己锁定在该项目的前沿编译器中。

【问题讨论】:

  • Eric Niebler 的__static_const 疯狂基本上是穷人的内联变量。

标签: c++ c++14 constexpr perfect-forwarding one-definition-rule


【解决方案1】:

如果您可以更改获取type 的方式,则可以通过使用枚举常量来避免更改您的makeFoo 调用,枚举常量评估为不同类型的纯右值:

template <typename> struct tag_helper;

enum { tagBar }; template <> struct tag_helper<decltype(tagBar)> { using type = Bar; };
enum { tagBaz }; template <> struct tag_helper<decltype(tagBaz)> { using type = Baz; };

auto myFoo = makeFoo("hi", ::tagBar, 42, ::tagBaz);

【讨论】:

  • 哇,枚举!而且您还解决了可能包含type typedef 的普通对象的问题,但运气不好。蛋糕给你:D
【解决方案2】:

使用constexpr 函数来生成标签怎么样?

constexpr struct tag_type { using type = ActualType; };

constexpr tag_type tag() { return {}; }

用法:

auto myFoo = makeFoo("hi", ::tagBar(), 42, ::tagBaz())

或者,您可以从它们的类型就地构造标签,而不是使用函数:

constexpr struct tag { using type = ActualType; };

用法:

auto myFoo = makeFoo("hi", ::tagBar{}, 42, ::tagBaz{})

【讨论】:

  • 如果我的标签是通过它们的类型命名的,我是否正确地说我不需要该函数,从而使tagBar()(或tagBar{})成为临时结构?
  • @Quentin 是的,应该也可以。我会将其添加到答案中。
【解决方案3】:

你可以让你的标签变量模板吗?

template <class T>
struct tag_t { using type = T; };

template <class T>
constexpr tag_t<T> tag{};

您将用作:

auto myFoo = makeFoo("hi", ::tag<Bar>, 42, ::tag<Baz>);

【讨论】:

  • 我想保留多个标签具有相同ActualType 的能力,所以我想这会变成::tagBar&lt;&gt;
【解决方案4】:

通常的方法不是简单地将 constexpr 标记定义声明为静态吗?

推测Foo的构造函数只对类型感兴趣,所以标签类型的多个模型的存在并不重要。

#include <utility>

struct Foo {
    template<class...Args>
            Foo(Args&&...args)
    {}

    // ...
};

template <class... Args>
Foo makeFoo(Args &&... args) {
    return {std::forward<Args>(args)...};
}

struct Bar {};
struct Baz {};

static constexpr struct tagBar_type { using type = Bar; } tagBar {};
static constexpr struct tagBaz_type { using type = Baz; } tagBaz {};

int main()
{
    auto myFoo = makeFoo("hi", ::tagBar, 42, ::tagBaz);
}

【讨论】:

  • 您仍然需要在某处定义一个标签,否则您会遇到其他 ODR 问题 - 比如如果两个不同的 TU 调用 makeFoo("hi", tagBar)
  • @Barry 如果我们正在处理标签模型,那有什么关系呢?我们只关心标签的类型。
猜你喜欢
  • 2019-06-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-13
  • 1970-01-01
  • 1970-01-01
  • 2012-12-20
相关资源
最近更新 更多