Search code examples
c++gccargument-dependent-lookupfriend-function

How to fix previously-working injected template friend function?


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!


Solution

  • 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.