My goal is simple: I want to specialize a method template for multiple types at once. I've googled around for possible ways to accomplish this task and found that concepts can be used for that.
I've created a simplified test program that resembles my target one to test how concepts work:
#include <iostream>
#include <concepts>
class A {
public:
std::string getName() const { return "A"; }
};
class B {
public:
std::string getName() const { return "B"; }
};
class C {
public:
std::string getName() const { return "C"; }
};
class D {
public:
std::string getName() const { return "D"; }
};
// Concept for classes C and D
template<typename T>
concept IsCorD = std::same_as<T, C> || std::same_as<T, D>;
// User class with the method template
class User {
public:
// Primary template declaration
template<typename T>
void doSomething();
};
// Specialization for A
template<>
void User::doSomething<A>() {
std::cout << "Specialized case for A" << std::endl;
}
// Specialization for B
template<>
void User::doSomething<B>() {
std::cout << "Specialized case for B" << std::endl;
}
// Partial specialization for C and D using concepts
template<IsCorD T>
void User::doSomething<T>() {
std::cout << "Case for C or D" << std::endl;
}
int main() {
User user;
user.doSomething<A>();
user.doSomething<B>();
user.doSomething<C>();
user.doSomething<D>();
return 0;
}
Here I'm trying to create a method template specialization for both C
and D
classes using the IsCorD
concept. I use the following command to compile it:
g++ main.cpp --std=c++20 -Wall -Wextra -Wpedantic -O3 -o main
And I get the following compilation error:
main.cpp:51:27: error: non-class, non-variable partial specialization ‘doSomething<T>’ is not allowed
51 | void User::doSomething<T>() {
| ^
main.cpp:51:6: error: no declaration matches ‘void User::doSomething()’
51 | void User::doSomething<T>() {
| ^~~~
main.cpp:33:10: note: candidate is: ‘template<class T> void User::doSomething()’
33 | void doSomething();
| ^~~~~~~~~~~
main.cpp:29:7: note: ‘class User’ defined here
29 | class User {
| ^~~~
What am I doing wrong? How to get it compiled and working as intended? The expected output is:
Specialized case for A
Specialized case for B
Case for C or D
Case for C or D
Community members have suggested that this question maybe a duplicate of Why can I seemingly define a partial specialization for function templates?. I checked answers to that question, and they suggest to use std::enable_if
to work around the forbidden partial specialization. But the answers there don't provide a solution using concepts.
The error occurs because C++ doesn't allow partial specialization of function templates (including member function templates).
That means you cannot write a version of doSomething<T>()
that is only enabled for types C and D by "partially specializing" the template. Instead of trying to specialize for types C and D partially, you can create separate overloads of your member functions and constrain them using concepts. Here's one thing that you can try to do:
#include <iostream>
#include <concepts>
#include <string>
// Definitions of classes A, B, C, and D.
class A {
public:
std::string getName() const { return "A"; }
};
class B {
public:
std::string getName() const { return "B"; }
};
class C {
public:
std::string getName() const { return "C"; }
};
class D {
public:
std::string getName() const { return "D"; }
};
// Concept that checks if a type is either C or D.
template<typename T>
concept IsCorD = std::same_as<T, C> || std::same_as<T, D>;
class User {
public:
// Overload for A
template<typename T>
requires std::same_as<T, A>
void doSomething() {
std::cout << "Specialized case for A" << std::endl;
}
// Overload for B
template<typename T>
requires std::same_as<T, B>
void doSomething() {
std::cout << "Specialized case for B" << std::endl;
}
// Overload for C and D
template<typename T>
requires IsCorD<T>
void doSomething() {
std::cout << "Case for C or D" << std::endl;
}
};
int main() {
User user;
user.doSomething<A>(); // Prints: Specialized case for A
user.doSomething<B>(); // Prints: Specialized case for B
user.doSomething<C>(); // Prints: Case for C or D
user.doSomething<D>(); // Prints: Case for C or D
return 0;
}
Instead, define multiple overloaded versions of doSomething
that are constrained using the requires clause and concepts:
One overload for type A (using requires std::same_as<T, A>).
One overload for type B.
One overload for types C or D (using the IsCorD concept).
So the output should look like this:
Specialized case for A
Specialized case for B
Case for C or D
Case for C or D
Hope this helps!