3

I have recently updated gcc compiler from version 5 to 8, and it has broken our production code. A simplified version of the broken code is included below:

#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>();
  }

In simplest terms, this code provides a way of capturing metadata around a tag (a type in the example above, but could also be an enum value) and was originally coded some 10+ years ago and has seen many gcc upgrades, but something "broke" in gcc 6 (verified via the famous godbolt online compiler).

It is quite possible that this code wasn't supported by the c++ standard and was just a gcc extension which has now been dropped, but I would be interested to know if this was actually the case and what the rationale might be for it being rejected by the standard.

It seems also that clang doesn't support this code either but I have noticed that if you do an ast-dump (clang -Xclang -ast-dump) that clang does at least hold the definitions of these friend functions, but it seems it is unable to find them when used (a template argument deduction failure?).

I would be very delighted to know of any work-around or alternative that works in as similar a way as possible, i.e. though some form of single line instantiation and, critically, only for tags that have been explicitly instantiated.

Specifically, what I don't want is to have a string of template functions that all have to be implemented per tag (I've just shown one metadata item and there are many in the production code, some of which derive further information from combinations of template arguments and/or other type information). The original solution developed above led to very clean, extendable and maintainable code. Wrapping it all in some complex macro would be the absolute worst-case scenario!

There is a similar question and answer here but I can't see how to make this solution work in this scenario since the argument to the friend function is not the parent class itself, but a template argument of it.

Changing the GetId function to take MetadataImpl<...> as its argument would not be a viable solution, since then the use of the functions becomes utterly impractical. The places where the functions are called from just want to provide the tag itself.

Thank you, in advance, for any help!

Andy G
  • 361
  • 1
  • 3
  • 11

3 Answers3

3

The reason it worked before is because gcc has bugs. It wasn't standard C++ and most probably won't ever be. But this is

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>{});
    }
}

And you get to write the exact same thing as before

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

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

Note the anonymous namespace, you run afoul the ODR if you don't have it.

Passer By
  • 19,325
  • 6
  • 49
  • 96
0

Why don't you just write GetId as a free function and specialize it as needed?

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

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

A regex replace can help you with transforming the class template explicit instantiations to these function template specializations. (This is one of the few circumstances under which specializing a function template would make sense.)


If you don't want a default value, just define the primary function as = delete: (C++11)

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

If you can use variable templates, (C++14) you can make the code look prettier:

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

template <> unsigned Id<int>    = 1;
template <> unsigned Id<double> = 2;
// ...
L. F.
  • 19,445
  • 8
  • 48
  • 82
  • 2
    I'm afraid this is exactly what I wish to avoid since this requires lots and lots of free functions to be "written" this way. Yes, I can wrap it all in a massive, horrible macro, but it becomes a real nightmare to maintain. I've considered also an external code generator, but again, it is not pretty given how much this coding paradigm has been used in the code in question. – Andy G Apr 28 '19 at 10:06
  • @AndyG Well, I kinda understand your situation now ... You just used this extension too extensively in your code, and transforming all of them to standard conforming code is a nightmare. What you want is a way to re-enable this extension. Well ... – L. F. Apr 28 '19 at 10:08
  • I fear you might be right... bonus points to anyone who can patch clang to support this via an extension flag!! – Andy G Apr 28 '19 at 10:11
0

So maybe this violates your "no strings of templates" requirement but you can use a tag helper struct:

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;
};

(That would also avoid the many explicit instantiations). The metadata implementation would be:

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

and now you can write a helper to ADL call GetId.

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

Demo.

Rakete1111
  • 47,013
  • 16
  • 123
  • 162