The program test tag dispatching pattern, where the function process_data(tag, a, b) can accept 2 tags int_tag, float_tag. There are 3 case:
int_tag and a, b are int -> print a + bfloat_tag and a,b are float -> print a * bunsupported for every other caseThe correct implementation is inside comment block. I'm trying to implement it another way, but current code always output unsupported isntead of calling the correct int/float version of process_data.
Why is the current implementation wrong? Compiled with -std=c++20
#include <iostream>
#include <type_traits>
#include <vector>
#include <string>
// ------------------------------------------------------------
// Tag types: int_tag and float_tag
// ------------------------------------------------------------
struct int_tag {};
struct float_tag {};
// ------------------------------------------------------------
// 2) Primary template: process_data (generic version)
// ------------------------------------------------------------
// template <typename Tag, typename T, typename U>
// void process_data(Tag tag, const T& a, const U& b) {
// std::cout << "<unsupported type>" << "\n";
// }
template <typename Tag, typename A, typename B, typename Enable = void>
void process_data(Tag tag, const A& a, const B& b) {
std::cout << "<unsupported type>" << "\n";
}
// ------------------------------------------------------------
// 3) Tag-dispatched function for int_tag (sum two integral numbers)
// ------------------------------------------------------------
// template <typename T, typename U>
// std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>, void>
// process_data(int_tag, const T& a, const U& b) {
// std::cout << "Result: " << (a + b) << "\n"; // Sum for integral types
// }
template <typename A, typename B, std::enable_if_t<std::is_integral_v<A> && std::is_integral_v<B>>>
void process_data(int_tag, const A& a, const B& b) {
std::cout << "Result: " << (a + b) << "\n";
}
// ------------------------------------------------------------
// 4) Tag-dispatched function for float_tag (multiply two floating-point numbers)
// ------------------------------------------------------------
// template <typename T, typename U>
// std::enable_if_t<std::is_floating_point_v<T> && std::is_floating_point_v<U>, void>
// process_data(float_tag, const T& a, const U& b) {
// std::cout << "Result: " << (a * b) << "\n"; // Product for floating-point types
// }
template <typename A, typename B, std::enable_if_t<std::is_floating_point_v<A> && std::is_floating_point_v<B>>>
void process_data(float_tag, const A& a, const B& b) {
std::cout << "Result: " << (a * b) << "\n";
}
// ------------------------------------------------------------
// TESTS (do NOT change)
// ------------------------------------------------------------
int main() {
bool all_ok = true;
// Test: process_data for int_tag
std::cout << "[process_data for int_tag]\n";
std::cout << "int: ";
process_data(int_tag{}, 10, 20); // Should print: Result: 30
std::cout << "float: ";
process_data(float_tag{}, 3.14f, 2.0f); // Should print: Result: 6.28
std::cout << "string: ";
process_data(int_tag{}, "Hello", "World"); // Should print: <unsupported type>
return 0;
}
Issue is with your enable_if_t usage.
With false condition SFINAE rejects it as intended, but with true condition
it becomes template <typename A, typename B, void> which is also rejected by SFINAE.
It should be something like:
template <typename A,
typename B,
std::enable_if_t<std::is_integral_v<A> && std::is_integral_v<B>>* = nullptr>
// ^^^^^^^^^^^
// add valid type and default
// or
// std::enable_if_t<std::is_integral_v<A> && std::is_integral_v<B>, bool> = true>
void process_data(int_tag, const A& a, const B& b) {
std::cout << "Result: " << (a + b) << "\n";
}
Demo
Your confusion is from
// 2) Primary template: process_data (generic version)
template <typename Tag, typename A, typename B, typename Enable = void>
void process_data(Tag, const A&, const B&);
There are no partial specializations for functions.
You just add overloads.
The typename Enable = void is so superfluous.
With C++20, you might simplify to:
template <std::integral A, std::integral B>
void process_data(int_tag, const A& a, const B& b) {
std::cout << "Result: " << (a + b) << "\n";
}
template <std::floating_point A, std::floating_point B>
void process_data(float_tag, const A& a, const B& b) {
std::cout << "Result: " << (a * b) << "\n";
}
Demo
Remark: the following doesn't answer your specific point (see the accepted answer for that)
Note however that instead of having some kind of runtime error (i.e. with a call to the "unsupported types" version), you could make sure at compile time that process_data can't be used with wrong arguments, and it is always better to detect errors at compile time rather than at runtime when possible.
You can do that with static_assert, e.g.
template <typename A, typename B>
void process_data(int_tag, const A& a, const B& b) {
static_assert (std::is_integral_v<A> && std::is_integral_v<B>);
std::cout << "Result: " << (a + b) << "\n";
}
template <typename A, typename B>
void process_data(float_tag, const A& a, const B& b) {
static_assert (std::is_floating_point_v<A> && std::is_floating_point_v<B>);
std::cout << "Result: " << (a * b) << "\n";
}
You don't need anymore to define a default process_data with a default "unsupported types" implementation
You can use it as follows:
int main() {
std::cout << "int : "; process_data(int_tag{}, 10, 20); // Should print: Result: 30
std::cout << "float : "; process_data(float_tag{}, 3.14f, 2.0f); // Should print: Result: 6.28
// would not compile: std::cout << "string: "; process_data(int_tag{}, "Hello", "World"); // Should print: <unsupported type>
return 0;
}
Demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With