如何修复以前工作的注入模板友元函数?
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。
我最近将 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 其他类型信息)。上面开发的原始解决方案导致了非常干净、可扩展和可维护的代码。将其全部包装在一些复杂的宏中绝对是最坏的情况!
有一个类似的问答
将 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。