c++template-specializationsfinaec++-conceptstemplate-function

limiting specializations using SFINAE, Constraints or Concepts?


The following program runs fine:

struct M; // forward declare so compiler will recognize this type
struct N;

template< typename J > struct B { template< typename U > void Func1(); };

template<> template<> void B< M >::Func1< M >() {}
template<> template<> void B< N >::Func1< N >() {}
template<> template<> void B< M >::Func1< N >() {} // illegal specialization for this app

template< typename J > struct C { template< typename U > void Func2(); };

template<> template<> void C< M >::Func2< M >() {}
template<> template<> void C< N >::Func2< N >() {}

template< typename G > struct H { G g; };

int main()
{
  H< B< M > > hbm;
  hbm.g.Func1< M >(); // ok
  hbm.g.Func1< N >(); // compiles and runs, but uses a semantically illegal specialization for this app

  H< B< N > > hbn;
  hbn.g.Func1< N >(); // ok

  H< C< M > > hcm;
  hcm.g.Func2< M >(); // ok

  H< C< N > > hcn;
  hcn.g.Func2< N >(); // ok

  return 0;
}

It is important that structs B and C be declared explicitly at compile time and that only those specializations that make sense to the app be allowed.

But as can be seen in the code above, it is possible for my downstream developers (some day!) would be able to create syntactically correct patterns that don't make sense semantically. In specific, the app only knows how to use types whose class and function types are equal. The rest are nonsensical.

This seems like a case for one of the new C++17+ capabilities like SFINAE, Constraints or Concepts. Although I am reading up on these, I don't have the judgement yet to make that choice. In cppreference under Alternatives, they suggest Concepts instead of SFINAE if the compiler is capable (I use VS2015).

What would be a good way to constrain typename J to be the same as typename U?


Solution

  • You can use enable_if:

    template< typename J > struct B {     
        template<typename U>
        typename std::enable_if<std::is_same<U, J>::value, void>::type
        Func1();
    };
    

    http://coliru.stacked-crooked.com/a/efb499cf654f0f25

    With concepts (not in standard in near(?) future) the same solution as above would look as below.

    http://melpon.org/wandbox/permlink/li4Uh5Q6ilpnlhcl

    template <class T, class U> concept bool Same = std::is_same<T,U>::value;
    
    template< typename J > struct B { 
        template< typename U > 
        requires Same<J, U>    
        void Func1(); 
    };