jeudi 27 mai 2021

Should template (template) specializations ignore template aliases or may an alias be treated as a distinct type

I'm having trouble getting a specialization to work for a template template class in combination with some aliases using clang. I made a template template class PointerHelper (to hide the differences in usage between normal STL smart pointers and a custom smart pointer I made). I made a specialization of PointerHelper for a template class Pointer, but where I want to use the specialization I've made a template alias for Pointer called MyPointer.

For some reason clang refuses to use the specialization and instead chooses the default implementation when I refer to PointerHelper with explicit template parameters. If I add a wrapper template function to auto-deduce the template arguments, everything works as I would expect. If I actually add a specialization for my alias, clang is happy and even uses the specialization for the alias, not the one for the actual underlying type.

After a couple of hours of trial and error, I went to trusty Compiler Explorer to check what MSVC and GCC would say. MSVC agrees with clang that my code is broken, but GCC accepts it as valid code. Normally I tend to assume that if the majority of compilers complain about my code I'm probably doing something wrong, but in this case I'm not so sure.

I came up with the following minimal example (https://godbolt.org/z/r86Ynr18j):

#include <stdio.h>

template <typename T>
struct Pointer
{
  operator bool() const
  {
    return true;
  }
};

template <template <class> class PointerType, typename T>
struct PointerHelper
{
};

template <typename T>
struct PointerHelper<Pointer, T>
{
  using TPointer = Pointer<T>;

  static bool IsInitialized(const TPointer& ptr)
  {
    printf("Pointer\n");
    return ptr;
  }
};

template <template <class> class PointerType, typename T>
struct DoesSomethingWithPointers
{
  using MyPointerHelper = PointerHelper<PointerType, T>;
  using TPointer = typename MyPointerHelper::TPointer;

  bool Initialized(TPointer ptr)
  {
    return MyPointerHelper::IsInitialized(ptr);
  }
};

template <typename T>
using MyPointer = Pointer<T>;

// Clang and MSVC require this specialization, GCC thinks it's a redefinition
// template <typename T>
// struct PointerHelper<MyPointer, T>
// {
//   using TPointer = Pointer<T>;

//   static bool IsInitialized(const TPointer& ptr)
//   {
//     printf("MyPointer\n");
//     return ptr;
//   }
// };

// A wrapper template to auto-deduce the arguments always selects the correct specialization
template <template <class> class Ptr, typename T>
bool IsPointerInitialized(Ptr<T> ptr)
{
  return PointerHelper<Ptr, T>::IsInitialized(ptr);
}

using Foo = int;

int main()
{
  Pointer<int> p;
  DoesSomethingWithPointers<Pointer, int> b;
    
  if (b.Initialized(p))
    printf("Initialized\n");

  if (IsPointerInitialized(MyPointer<int>()))
    printf("Initialized\n");

  //fails on clang and MSVC if 2nd specialization isn't present: 
  DoesSomethingWithPointers<MyPointer, int> b2;
  if (b2.Initialized(p))
    printf("Initialized\n");
  return 0;
}

As mentioned in the comments in the example, both clang and MSVC require me to make a specialization for the alias (and they use that 2nd specialization when executing the code) when calling the template with explicit parameters, even though according to my understanding an alias is not a distinct type, but only a different identifier for the same underlying type. Using the wrapper template function to auto-deduce the template parameters fixes the problem. GCC behaves as I would expect: it doesn't require the alias-specialization and even complains that I'm redefining the specialization.

Which of the compilers is correct? (or are they all correct and am I just doing something that's not specified, but GCC is friendly enough to accept it anyway?).

I tried to do a web search to see if this is a known issue, but I couldn't find anything (which could very well be caused by me not using the correct terminology).

Aucun commentaire:

Enregistrer un commentaire