template class method specialization using concepts

344 views Asked by At

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.

2

There are 2 answers

0
Jacob Bischoff On BEST ANSWER

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;
}
1
visard On

You have to add a new template parameter, which will default to T and to which you can add your constraint of std::integral<>. This compiled with gcc10.3.0 and clang12.0.0, other versions you will have to test yourself.

The code:

#include <concepts>
#include <string>

template <typename T>
struct A
{
    template <typename U = T>
    requires std::integral<U>
    void f();
};

template <typename T>
template <typename U>
requires std::integral<U>
void A<T>::f()
{

}

int main()
{
    A<int> a;
    a.f();

    A<std::string> s;  // This works
    // s.f();  // Compilation error: constraint not satisfied
    return 0;
}