如何修复以前工作的注入模板友元函数?

How to fix previously-working injected template friend function?

我最近将 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 中的东西 "broke"(通过著名的 godbolt 在线编译器验证)。

很可能此代码不受 c++ 标准支持,只是一个 gcc 扩展,现在已被删除,但我很想知道这是否真的如此,其基本原理可能是什么是因为它被标准拒绝了。

似乎 clang 也不支持这段代码,但我注意到如果你做一个 ast-dump (clang -Xclang -ast-dump) clang 至少保存了这些友元函数的定义,但是使用时似乎无法找到它们(模板参数推导失败?)。

我很高兴知道任何以尽可能相似的方式工作的解决方法或替代方案,即虽然某种形式的单行实例化,并且关键,仅适用于已显式实例化的标签。

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

有一个类似的问答 但我看不出如何使这个解决方案在这种情况下起作用,因为友元函数的参数不是父函数 class 本身,但是它的一个模板参数。

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

提前感谢您的帮助!

为什么不直接将 GetId 编写为一个自由函数并根据需要对其进行专门化?

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

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

正则表达式替换可以帮助您将 class 模板显式实例化转换为这些函数模板特化。 (这是对函数模板进行特殊化处理的少数情况之一。)


如果您不需要默认值,只需将主函数定义为 = 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;
// ...

所以这可能违反了您的 "no strings of templates" 要求,但您可以使用 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.

之前能用的原因是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。