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

时间:2019-04-28 09:50:39

标签: c++ gcc argument-dependent-lookup 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中出现了一些“中断”(已通过著名的godbolt在线编译器进行了验证)。

很可能该代码不受c ++标准支持,只是一个gcc扩展,现已被删除,但是我想知道这是否是实际情况,以及其原理是什么被标准拒绝了。

似乎clang也不支持此代码,但我注意到,如果您执行ast-dump(clang -Xclang -ast-dump),clang至少会保留这些朋友函数的定义,但是似乎使用时找不到它们(模板参数推导失败?)。

我将非常高兴得知任何变通方法或替代方法都以尽可能相似的方式工作,即某种形式的单行实例化,并且关键的是,仅针对那些已被明确实例化。

具体地说,我不想要的是拥有一串模板功能,所有这些功能必须针对每个标签实施(我刚刚展示了一个元数据项,并且在产品中有很多代码,其中一些可从模板参数和/或其他类型信息的组合中获得更多信息。上面开发的原始解决方案导致代码非常干净,可扩展和可维护。将所有内容包装在一个复杂的宏中绝对是最坏的情况!

有一个类似的问答here,但我看不到如何在这种情况下使用此解决方案,因为friend函数的参数不是父类本身,而是其模板参数

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

在此先感谢您的帮助!

3 个答案:

答案 0 :(得分:2)

它之所以起作用,是因为gcc有错误。它不是标准的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的情况下运行。

答案 1 :(得分: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;
// ...

答案 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