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 Answers 2
There are four syntactical methods of applying constraints to a function.
- Type constraint in a template parameter list;
template< Concept TypeID >. - Requires clause after a template parameter list;
template< class TypeID > requires constexpr-andor-requires-expression. - Constraint on auto in an abbriviated function template;
void f(Concept auto id);. - 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;
}
Comments
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;
}
1 Comment
A<std::string>().f<int>(); which will compile just fine.Explore related questions
See similar questions with these tags.
clangnorMSVCcan compile your second example.