Search code examples
visual-studioc++14template-specializationcontent-assisttemplate-classes

How to elegantly restrict a template argument to be a `<Certain_Class<AnyT>>`?


How to restrict a template argument of Wrapper to be a Wrapper<Any,MyArray<AnyT>> elegantly?

  1. Don't break content assist (Visual Studio).
  2. High readability. Not use a hacky approach.
    For some reasons, most solution love to hack.
  3. Make it obvious at the very first line by using C++ syntax rule. (not just green comment)

As far as I know, there are many solutions, but every solution doesn't meet the criteria.

Workaround 1 (template specialization, fail 1)

template<class T> class MyArray{};
template<class T,class T2> class Wrapper;
template<class T,class T2> class Wrapper<T,MyArray<T2>>{     
    using Test=int; 
};
class B{};
class C{};
int main() {
    Wrapper<C,MyArray<B>> wrapper;
    return 0;
}

This code is modified from https://stackoverflow.com/a/43518221 (@max66).

Context clue / syntax highlighting of IDE will be confused.
In my case, it marks some correct types as error e.g. :-

class ShowError : public Wrapper<B,MyArray<C>>{ 
    Test n=0;  //<-- unknown "Test" (intellisense)
};

Workaround 2 (some hacky field/typedef, fail 2)

template<class T> class MyArray{ 
    public: using MyArrayT=T; 
};
template<class T,class T2> class Wrapper{
    public: using myT=typename T2::MyArrayT;
    //^ assert at compile time
};

This idea come from a comment in https://stackoverflow.com/a/43518295 (@Jarod42)

The class declaration doesn't mention about MyArray, it just uses a hacky (less readable) way (MyArrayT) to enforce that T2 is MyArray.

Workaround 3 (base class, fail 2)

class MyArrayBase{};
template<class T> class MyArray : public MyArrayBase{  };
template<class T,class T2> class Wrapper{
    //check something around MyArrayBase *object = new T2();
    // or "is_base_of"
};

The code is modified from Restrict C++ Template Parameter to Subclass and C++ templates that accept only certain types.

It has same disadvantage as workaround 2.
It is not obvious for common user.

Workaround 4 (SNIFAE, fail 1)

By adding std::enable_if on the template class declaration (Wrapper), I can get a working hack.
Unfortunately, content assist hate it.

Reference

Here are the other links that I read :-


Solution

  • You can write a custom type trait is_specialization, as follows:

    template<class Type, template<class...> class Template>
    struct is_specialization
      : std::false_type {};
    template<template<class...> class Template, class... TArgs>
    struct is_specialization<Template<TArgs...>, Template>
      : std::true_type {};
    

    Then you just need to static_assert that is_specialization is true for the given template argument:

    template<class T,class T2>
    class Wrapper {
        static_assert(is_specialization<T2, MyArray>::value, "T2 must be a specialization of MyArray");
    };