Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ template dispatching not calling the correct function

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 + b
  • float_tag and a,b are float -> print a * b
  • print unsupported for every other case

The 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;
}
like image 709
Huy Le Avatar asked Mar 23 '26 08:03

Huy Le


2 Answers

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

like image 68
Jarod42 Avatar answered Mar 24 '26 22:03

Jarod42


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

like image 36
abcdefg Avatar answered Mar 24 '26 20:03

abcdefg