c++templatesmetaprogrammingenable-iftag-dispatching

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:

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;
}

Solution

  • 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