如何修复以前工作的注入模板朋友功能?

问题描述 投票:3回答:3

我最近更新了gcc编译器从版本5到8,它已经破坏了我们的生产代码。下面包含简化版本的破损代码:

#include <utility>

// Imagine this has several template parameters not just Id and
// this class provides lots of friend functions for retrieving
// all this "metadata". Just one is implemented in this example.
template <typename Tag, unsigned Id>
class MetadataImpl
  {
  template <typename T, typename U>
  using matches =
    typename std::enable_if<std::is_same<T, U>::value>::type;

  template <typename _Tag, typename = matches<_Tag, Tag>>
  friend unsigned GetId(Tag* = nullptr)
    { return Id; }
  };

// Let's generate some instances...
template class MetadataImpl<int, 1>;
template class MetadataImpl<double, 2>;

// And a simple test function.
unsigned test()
  {
  return GetId<int>();
  }

简单来说,这段代码提供了一种捕获标签周围元数据的方法(上面例子中的类型,但也可能是enum值),并且最初在大约10年前编码并且已经看到许多gcc升级,但是“打破了“在gcc 6中(通过着名的godbolt在线编译器验证)。

这个代码很可能不受c ++标准的支持,而且只是一个gcc扩展,现在已经被删除了,但我很想知道这是否真的是这种情况以及它被拒绝的理由是什么按标准。

似乎clang也不支持这个代码,但我注意到如果你做一个ast-dump(clang -Xclang -ast-dump),clang确实至少持有这些朋友函数的定义,但似乎它无法找到它们使用过(模板参数演绎失败?)。

我非常高兴知道任何以尽可能类似的方式工作的替代方案或替代方案,即通过某种形式的单行实例化,并且关键地,仅用于已经明确实例化的标签。

具体来说,我不想要的是有一串模板函数,每个标签都必须实现(我刚刚显示了一个元数据项,生产代码中有很多,其中一些从组合中获取更多信息)模板参数和/或其他类型信息)。上面开发的原始解决方案导致了非常干净,可扩展和可维护的代码。在一些复杂的宏中包装它将是绝对最坏的情况!

有一个类似的问题和答案here但我无法看到如何使此解决方案在这种情况下工作,因为友元函数的参数不是父类本身,而是它的模板参数。

改变GetId函数以将MetadataImpl<...>作为其论证将不是一个可行的解决方案,因为这时函数的使用变得完全不切实际。调用函数的地方只是想提供标记本身。

预先感谢您的任何帮助!

c++ gcc argument-dependent-lookup friend-function
3个回答
2
投票

它以前工作的原因是因为gcc有bug。它不是标准的C ++,很可能永远都不会。但这是

namespace 
{
    template<typename T>
    struct flag
    {
        friend constexpr unsigned adl(flag<T>);
    };

    template <typename T, unsigned n>
    class meta
    {
        friend constexpr unsigned adl(flag<T>)
        {
            return n;
        }
    };

    template<typename T>
    constexpr auto getId()
    {
        return adl(flag<T>{});
    }
}

你可以像以前一样写出完全相同的东西

template class meta<int, 1>;
template class meta<double, 2>;

auto foo()
{
    return getId<int>();
}

请注意匿名命名空间,如果您没有ODR,请与ODR进行冲突。


0
投票

你为什么不把GetId写成自由函数并根据需要专门化它?

template <typename Tag>
unsigned GetId()
{
  return /* default value */;
}

template <> unsigned GetId<int>   () { return 1; }
template <> unsigned GetId<double>() { return 2; }
// ...

正则表达式替换可以帮助您将类模板显式实例转换为这些函数模板特化。 (这是专门针对功能模板有意义的少数情况之一。)


如果您不想要默认值,只需将主要函数定义为= delete:(C ++ 11)

template <typename Tag>
unsigned GetId() = delete;

如果你可以使用变量模板(C ++ 14),你可以使代码看起来更漂亮:

template <typename Tag>
unsigned Id = /* default value */;

template <> unsigned Id<int>    = 1;
template <> unsigned Id<double> = 2;
// ...

0
投票

因此,这可能违反了您的“无模板字符串”要求,但您可以使用tag帮助程序结构:

template <typename T> struct tag {};
template <> struct tag<int> {
    static constexpr unsigned Id = 1;
    // any more customization points here
};
template <> struct tag<double> {
    static constexpr unsigned Id = 2;
};

(这也可以避免许多明确的实例化)。元数据实现将是:

template <typename Tag>
class MetadataImpl
  {
  friend unsigned GetId(MetadataImpl)
    { return Tag::Id; }
  };

现在你可以写一个帮助ADL调用GetId

template <typename T>
unsigned GetId() {
  return GetId(MetadataImpl<tag<T>>());
}

Demo

© www.soinside.com 2019 - 2024. All rights reserved.