c++c++20template-specializationconceptg++10

template class method specialization using concepts


There is a template class A with template parameter T. I want this class to have a method f if T is of integral types. The class A also has a lot of other methods, so I don't want to have specialization of overall A. I understand that this problem can be solved using inheritance, but my question is about concepts and requirements.

This code

  template <typename T>
  struct A {
    void f();
  };

  template <>
  void A<int>::f() {}

works as I expect. It makes implementation of f for the int type only. If I try to call A<std::string>{}.f(); it generates a linker error as expected. But if I write

  template <typename T>
  struct A {
    void f();
  };

  template <std::integral T>
  void A<T>::f() {}

either

  template <typename T> requires std::is_integral_v<T>
  void A<T>::f() {}

the method f is generated for all types, so calling A<std::string>{}.f(); does not give any error.


Also this works

  template <typename T>
  struct A {
    void f() {}
  };

  template <>
  void A<std::string>::f() = delete;

but this

  template <typename T>
  struct A {
    void f() {}
  };

  template <std::integral T>
  struct A<T>::f() = delete;

gives compilation error, namely redefinition of f.


P.S. It seems such constructions are not allowed at all, but g++ just ignores concepts in definition of method f.


Solution

  • There are four syntactical methods of applying constraints to a function.

    1. Type constraint in a template parameter list; template< Concept TypeID >.
    2. Requires clause after a template parameter list; template< class TypeID > requires constexpr-andor-requires-expression.
    3. Constraint on auto in an abbriviated function template; void f(Concept auto id);.
    4. Requires clause after a function declaration; template< class TypeID > void f() requires constexpr-andor-requires-expression.

    The function you want to constrain doesn't have a template parameter list so you can't use methods 1 and 2. Method 3 essentially generates template parameters. So that leaves method 4.

    #include <concepts>
    
    template< class T >
    struct A {
        void f() { /* do something */ }
        void g() requires std::integral<T> { /* do something */ }
        void h() requires std::integral<T>;
        template< std::integral U = T >
        void i() { /* do something */ }
    };
    
    template< class T >
    void A<T>::h() requires std::integral<T> { /* do something */ }
    
    
    int main() {
        A<double> dblA;
        dblA.f();
        // dblA.g();        // A<double>::g() is not declared or defined
        // dblA.h();        // A<double>::h() is not declared or defined
        // dblA.i();        // A<double>::h<double>() is not declared or defined
        dblA.i<int>();      // A<double>::h<int>() is declared and defined
    
        A<int> intA;
        intA.f();
        intA.g();           // A<int>::g() is declared and defined
        intA.h();           // A<int>::h() is declared and defined
        intA.i();           // A<int>::h<int>() is declared and defined
        //intA.i<double>(); // A<int>::h<double>() is not declared or defined
    
        return 0;
    }